From 77bd4fabb68c2db0e5f687ea15dce4435d27657b Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Nov 2025 19:35:48 +0100 Subject: [PATCH] fixed -inf --- .tf5_mixer_cache/channels_cache.json | 4 +- __pycache__/mixer_controller.cpython-312.pyc | Bin 36857 -> 38110 bytes conversations/4762864.json | 144 ++++--- mixer_controller.py | 379 ++++++++++--------- 4 files changed, 304 insertions(+), 223 deletions(-) diff --git a/.tf5_mixer_cache/channels_cache.json b/.tf5_mixer_cache/channels_cache.json index 2155302..1e52135 100644 --- a/.tf5_mixer_cache/channels_cache.json +++ b/.tf5_mixer_cache/channels_cache.json @@ -9,9 +9,9 @@ }, "2": { "channel": 2, - "name": "Gelato", + "name": "0.00", "on": false, - "level_db": null, + "level_db": 0.01, "pan": 0 }, "3": { diff --git a/__pycache__/mixer_controller.cpython-312.pyc b/__pycache__/mixer_controller.cpython-312.pyc index 3b300129bae402521394856e171050e25863300d..5352bad997f13cb0cfd238661115fcfb9aade13a 100644 GIT binary patch delta 8651 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 diff --git a/conversations/4762864.json b/conversations/4762864.json index 2e4b56f..54a25b3 100644 --- a/conversations/4762864.json +++ b/conversations/4762864.json @@ -1,87 +1,135 @@ { "user_id": 4762864, "username": "unnikked", - "last_updated": 1762194065.7709692, - "last_updated_str": "2025-11-03 19:21:05", - "message_count": 13, + "last_updated": 1762194931.602051, + "last_updated_str": "2025-11-03 19:35:31", + "message_count": 21, "history": [ + { + "role": "assistant", + "content": "Fatto! Il canale \"Gelato\" dovrebbe essere acceso ora. Fammi sapere se è tutto ok! 🍦✅", + "timestamp": 1762194705.2052844, + "timestamp_str": "2025-11-03 19:31:45" + }, { "role": "user", - "content": "Me lo spegni il gelato", - "timestamp": 1761595234.0849943, - "timestamp_str": "2025-10-27 21:00:34" + "content": "Alzami un po' il gelato", + "timestamp": 1762194720.6919649, + "timestamp_str": "2025-11-03 19:32:00" }, { "role": "assistant", - "content": "Fatto! Canale \"Gelato\" spento. ✅", - "timestamp": 1761595239.1608367, - "timestamp_str": "2025-10-27 21:00:39" + "content": "Ho alzato un po' il volume del \"Gelato\" a -11.3 dB. 🍦🔊 Fammi sapere se va meglio così!", + "timestamp": 1762194727.1430511, + "timestamp_str": "2025-11-03 19:32:07" }, { "role": "user", - "content": "Mi accendi il gelato.", - "timestamp": 1761595383.8880353, - "timestamp_str": "2025-10-27 21:03:03" + "content": "Alza ancora il gelato", + "timestamp": 1762194746.9937618, + "timestamp_str": "2025-11-03 19:32:26" }, { "role": "assistant", - "content": "Certo! Gelato acceso! 🍦✅", - "timestamp": 1761595387.27931, - "timestamp_str": "2025-10-27 21:03:07" + "content": "Ancora un po' su per il \"Gelato\"! Ora è a -11.3 dB. 🍦🔊", + "timestamp": 1762194751.8532023, + "timestamp_str": "2025-11-03 19:32:31" }, { "role": "user", - "content": "Apaga el helado. Cappuccino.", - "timestamp": 1761595426.0112157, - "timestamp_str": "2025-10-27 21:03:46" + "content": "Alzami tutto il gelato", + "timestamp": 1762194765.1222134, + "timestamp_str": "2025-11-03 19:32:45" }, { "role": "assistant", - "content": "Gelato spento! ✅\nNon ho trovato nessun canale chiamato \"Cappuccino\", sei sicuro del nome? 🤔", - "timestamp": 1761595431.5373232, - "timestamp_str": "2025-10-27 21:03:51" + "content": "Non riesco a trovare un canale chiamato \"Gelato\" in questo momento. È possibile che il nome sia cambiato o che non sia più disponibile. Potresti darmi il numero del canale o un altro nome per favore? 🤔", + "timestamp": 1762194785.8956504, + "timestamp_str": "2025-11-03 19:33:05" }, { "role": "user", - "content": "Okay, aprimi il gelato allora!", - "timestamp": 1762193935.418264, - "timestamp_str": "2025-11-03 19:18:55" - }, - { - "role": "user", - "content": "Gli alzi il gelato?", - "timestamp": 1762193958.1497903, - "timestamp_str": "2025-11-03 19:19:18" - }, - { - "role": "user", - "content": "Aprimi il giubbotto.", - "timestamp": 1762194045.3195145, - "timestamp_str": "2025-11-03 19:20:45" + "content": "Riprovarci", + "timestamp": 1762194813.0377226, + "timestamp_str": "2025-11-03 19:33:33" }, { "role": "assistant", - "content": "Non posso aprirti il giubbotto, non è un canale o un mix del mixer! 😄\n\nPosso aiutarti con i canali audio, i microfoni o i monitor, se vuoi.", - "timestamp": 1762194046.8080468, - "timestamp_str": "2025-11-03 19:20:46" + "content": "Mi dispiace, ho provato di nuovo ma non riesco ancora a trovare nessun canale chiamato \"Gelato\". 🤔\n\nSe il nome è cambiato o se c'è un numero di canale, potresti dirmelo? Così posso aiutarti!", + "timestamp": 1762194819.4210932, + "timestamp_str": "2025-11-03 19:33:39" }, { "role": "user", - "content": "Aprimi il gelato", - "timestamp": 1762194053.0858698, - "timestamp_str": "2025-11-03 19:20:53" + "content": "Ch2", + "timestamp": 1762194830.624848, + "timestamp_str": "2025-11-03 19:33:50" }, { "role": "assistant", - "content": "Certo! Gelato acceso! 🍦✅", - "timestamp": 1762194054.6322277, - "timestamp_str": "2025-11-03 19:20:54" + "content": "Ok, ho alzato il canale 2 al massimo (+10 dB)! 🔊 Fammi sapere se va bene così!", + "timestamp": 1762194834.0072317, + "timestamp_str": "2025-11-03 19:33:54" }, { "role": "user", - "content": "È ancora chiuso", - "timestamp": 1762194065.7709692, - "timestamp_str": "2025-11-03 19:21:05" + "content": "Portalo a zero db", + "timestamp": 1762194846.3105323, + "timestamp_str": "2025-11-03 19:34:06" + }, + { + "role": "assistant", + "content": "Perfetto! Ho riportato il canale 2 a 0 dB. ✅🔊", + "timestamp": 1762194848.8269963, + "timestamp_str": "2025-11-03 19:34:08" + }, + { + "role": "user", + "content": "Analizzami l'equilibrio del mix diretta, che suggerimenti dai?", + "timestamp": 1762194868.3175187, + "timestamp_str": "2025-11-03 19:34:28" + }, + { + "role": "assistant", + "content": "Mi dispiace, ma non posso analizzare l'equilibrio del mix o darti suggerimenti su come suona. Sono un assistente per controllare il mixer, posso modificare i volumi, i pan, accendere/spegnere i canali e i mix, ma non ho \"orecchie\" per ascoltare e valutare il suono. 😅\n\nPosso aiutarti a regolare i livelli o il pan se mi dici cosa vuoi cambiare!", + "timestamp": 1762194871.190547, + "timestamp_str": "2025-11-03 19:34:31" + }, + { + "role": "user", + "content": "E sì sui volumi", + "timestamp": 1762194884.4026275, + "timestamp_str": "2025-11-03 19:34:44" + }, + { + "role": "assistant", + "content": "Capito! Sui volumi posso agire, ma non posso dirti io cosa suona bene o male, non ho le \"orecchie\" per quello. 😅\n\nSe mi dici quale canale o mix vuoi alzare o abbassare, e di quanto, lo faccio subito! Dimmi tu cosa vuoi cambiare. 🔊", + "timestamp": 1762194886.4130292, + "timestamp_str": "2025-11-03 19:34:46" + }, + { + "role": "user", + "content": "Non preoccuparti, dammi un giudizio sui valori se torna tutto", + "timestamp": 1762194903.6934974, + "timestamp_str": "2025-11-03 19:35:03" + }, + { + "role": "assistant", + "content": "Mi dispiace, ma non posso darti un giudizio sui valori o dirti se \"torna tutto\" a livello di suono. Non ho la capacità di ascoltare e valutare l'audio come farebbe una persona. 😅\n\nPosso solo eseguire i comandi che mi dai per modificare i volumi, il pan o accendere/spegnere i canali e i mix. Se mi dici cosa vuoi cambiare, lo faccio subito!", + "timestamp": 1762194906.4559038, + "timestamp_str": "2025-11-03 19:35:06" + }, + { + "role": "user", + "content": "Potresti gentilmente farmi questa caspita di analisi? Poi ti dico io cosa fare", + "timestamp": 1762194928.5493388, + "timestamp_str": "2025-11-03 19:35:28" + }, + { + "role": "assistant", + "content": "Mi dispiace davvero, ma non posso fare questa \"caspita di analisi\" che mi chiedi. 😔 Non ho le orecchie per ascoltare il suono e darti un giudizio su come sta andando il mix o se i valori \"tornano\". Sono un assistente per controllare il mixer, posso solo eseguire i comandi che mi dai per modificare i parametri.\n\nSe mi dici:\n- \"Alza il canale 5\"\n- \"Abbassa il mix 3\"\n- \"Metti il pan del canale 10 a sinistra\"\n- \"Accendi il microfono del cantante\"\n\n...allora posso aiutarti subito! Ma non posso dirti io cosa suona bene o male. Dimmi tu cosa vuoi cambiare sui volumi o sul bilanciamento! 🔊", + "timestamp": 1762194931.602051, + "timestamp_str": "2025-11-03 19:35:31" } ] } \ No newline at end of file diff --git a/mixer_controller.py b/mixer_controller.py index abd3275..833505d 100644 --- a/mixer_controller.py +++ b/mixer_controller.py @@ -1,15 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- import socket import sys import os import json import time +import math from pathlib import Path from typing import List, Optional from google import genai from google.genai import types from dotenv import load_dotenv -load_dotenv() +load_dotenv() from config import * @@ -21,7 +24,23 @@ class TF5MixerController: self.socket = None self._ensure_cache_dir() self._cache = self._load_cache() - + + def _sanitize_value(self, value): + """ + Sanitizza i valori per renderli JSON-safe. + Converte -Infinity in -120 (silenzio pratico). + """ + if value is None: + return -120.0 + if value == float('-inf') or (isinstance(value, float) and math.isinf(value) and value < 0): + return -120.0 # Valore molto basso ma valido + elif value == float('inf') or (isinstance(value, float) and math.isinf(value) and value > 0): + return 10.0 # Massimo pratico + elif isinstance(value, float) and math.isnan(value): + return 0.0 # Default a 0 se NaN + + return value + def _connect(self): """Stabilisce la connessione se non già connesso.""" if self.socket is None: @@ -33,7 +52,7 @@ class TF5MixerController: except socket.error as e: self.socket = None raise ConnectionError(f"Impossibile connettersi al mixer: {e}") - + def _disconnect(self): """Chiude la connessione.""" if self.socket: @@ -46,11 +65,11 @@ class TF5MixerController: pass finally: self.socket = None - + def _send_command(self, command: str) -> str: """Invia un comando al mixer e restituisce la risposta.""" max_retries = 2 - + for attempt in range(max_retries): try: print(f'Tentativo di connessione {attempt} per {command}') @@ -60,33 +79,33 @@ class TF5MixerController: decoded = response.decode('utf-8', errors='ignore').strip() print(f'Risposta {decoded}') return decoded - + except socket.error as e: print(f'Errore di connessione dopo {max_retries} tentativi: {e}') self._disconnect() - + if attempt < max_retries - 1: time.sleep(0.1) continue else: return f"Errore di connessione dopo {max_retries} tentativi: {e}" - + def close(self): """Chiude la connessione (da chiamare alla fine).""" self._disconnect() - + def __enter__(self): """Context manager entry.""" return self - + def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit.""" self.close() - + def _ensure_cache_dir(self): """Crea la directory di cache se non esiste.""" CACHE_DIR.mkdir(parents=True, exist_ok=True) - + def _load_cache(self) -> dict: """Carica la cache dal file.""" if CACHE_FILE.exists(): @@ -96,18 +115,18 @@ class TF5MixerController: except Exception as e: print(f"⚠️ Errore nel caricamento della cache: {e}") return {"channels": {}, "mixes": {}, "timestamp": 0} - + def _save_cache(self): """Salva la cache nel file, convertendo -inf in null.""" try: # Crea una copia della cache per la serializzazione cache_to_save = self._prepare_cache_for_json(self._cache) - + with open(CACHE_FILE, 'w', encoding='utf-8') as f: json.dump(cache_to_save, f, indent=2, ensure_ascii=False) except Exception as e: print(f"⚠️ Errore nel salvataggio della cache: {e}") - + def _prepare_cache_for_json(self, obj): """Converte ricorsivamente -inf in null per la serializzazione JSON.""" if isinstance(obj, dict): @@ -121,39 +140,39 @@ class TF5MixerController: return obj else: return obj - + def _is_cache_valid(self) -> bool: """Verifica se la cache è ancora valida.""" if not self._cache.get("channels"): return False cache_age = time.time() - self._cache.get("timestamp", 0) return cache_age < CACHE_DURATION - + def _normalize_level_from_cache(self, level_value): """Normalizza il valore del livello dalla cache (gestisce None come -inf).""" if level_value is None: return float('-inf') return level_value - + def _update_channel_cache(self, channel: int): """Aggiorna la cache per un singolo canale leggendo dal mixer. - + Args: channel: Numero del canale (1-40) """ if not 1 <= channel <= TF5_INPUT_CHANNELS: return - + ch_idx = channel - 1 - + # Leggi nome resp_name = self._send_command(f"get MIXER:Current/InCh/Label/Name {ch_idx} 0") name = self._parse_name(resp_name) - + # Leggi stato ON/OFF resp_on = self._send_command(f"get MIXER:Current/InCh/Fader/On {ch_idx} 0") is_on = self._parse_value(resp_on) == "1" - + # Leggi livello resp_level = self._send_command(f"get MIXER:Current/InCh/Fader/Level {ch_idx} 0") level_raw = self._parse_value(resp_level) @@ -162,7 +181,7 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + # Leggi pan resp_pan = self._send_command(f"get MIXER:Current/InCh/ToSt/Pan {ch_idx} 0") pan_raw = self._parse_value(resp_pan) @@ -170,11 +189,11 @@ class TF5MixerController: pan_value = int(pan_raw) except: pan_value = None - + # Inizializza la struttura channels se non esiste if "channels" not in self._cache: self._cache["channels"] = {} - + # Aggiorna la cache self._cache["channels"][str(channel)] = { "channel": channel, @@ -183,28 +202,28 @@ class TF5MixerController: "level_db": level_db, "pan": pan_value } - + self._save_cache() - + def _update_mix_cache(self, mix_number: int): """Aggiorna la cache per un singolo mix/aux leggendo dal mixer. - + Args: mix_number: Numero del mix (1-20) """ if not 1 <= mix_number <= TF5_MIX_BUSSES: return - + mix_idx = mix_number - 1 - + # Leggi nome resp_name = self._send_command(f"get MIXER:Current/Mix/Label/Name {mix_idx} 0") name = self._parse_name(resp_name) - + # Leggi stato ON/OFF resp_on = self._send_command(f"get MIXER:Current/Mix/Fader/On {mix_idx} 0") is_on = self._parse_value(resp_on) == "1" - + # Leggi livello resp_level = self._send_command(f"get MIXER:Current/Mix/Fader/Level {mix_idx} 0") level_raw = self._parse_value(resp_level) @@ -213,11 +232,11 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + # Inizializza la struttura mixes se non esiste if "mixes" not in self._cache: self._cache["mixes"] = {} - + # Aggiorna la cache self._cache["mixes"][str(mix_number)] = { "mix": mix_number, @@ -225,9 +244,9 @@ class TF5MixerController: "on": is_on, "level_db": level_db } - + self._save_cache() - + def _parse_name(self, response: str) -> str: """Estrae il nome tra virgolette dalla risposta.""" try: @@ -238,36 +257,36 @@ class TF5MixerController: return "Sconosciuto" except: return "Errore" - + def _parse_value(self, response: str) -> str: """Estrae l'ultimo valore da una risposta OK.""" parts = response.split() if len(parts) > 0 and parts[0] == "OK": return parts[-1] return "N/A" - + def refresh_cache(self) -> dict: """Aggiorna la cache leggendo tutti i canali e mix dal mixer. - + Returns: Un dizionario con lo stato dell'operazione """ print("🔄 Aggiornamento cache completo in corso...") channels_data = {} mixes_data = {} - + # Aggiorna canali for ch in range(1, TF5_INPUT_CHANNELS + 1): ch_idx = ch - 1 - + # Leggi nome resp_name = self._send_command(f"get MIXER:Current/InCh/Label/Name {ch_idx} 0") name = self._parse_name(resp_name) - + # Leggi stato ON/OFF resp_on = self._send_command(f"get MIXER:Current/InCh/Fader/On {ch_idx} 0") is_on = self._parse_value(resp_on) == "1" - + # Leggi livello resp_level = self._send_command(f"get MIXER:Current/InCh/Fader/Level {ch_idx} 0") level_raw = self._parse_value(resp_level) @@ -276,7 +295,7 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + # Leggi pan resp_pan = self._send_command(f"get MIXER:Current/InCh/ToSt/Pan {ch_idx} 0") pan_raw = self._parse_value(resp_pan) @@ -284,7 +303,7 @@ class TF5MixerController: pan_value = int(pan_raw) except: pan_value = None - + channels_data[str(ch)] = { "channel": ch, "name": name, @@ -292,21 +311,21 @@ class TF5MixerController: "level_db": level_db, "pan": pan_value } - + time.sleep(0.05) - + # Aggiorna mix for mix in range(1, TF5_MIX_BUSSES + 1): mix_idx = mix - 1 - + # Leggi nome resp_name = self._send_command(f"get MIXER:Current/Mix/Label/Name {mix_idx} 0") name = self._parse_name(resp_name) - + # Leggi stato ON/OFF resp_on = self._send_command(f"get MIXER:Current/Mix/Fader/On {mix_idx} 0") is_on = self._parse_value(resp_on) == "1" - + # Leggi livello resp_level = self._send_command(f"get MIXER:Current/Mix/Fader/Level {mix_idx} 0") level_raw = self._parse_value(resp_level) @@ -315,23 +334,23 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + mixes_data[str(mix)] = { "mix": mix, "name": name, "on": is_on, "level_db": level_db } - + time.sleep(0.05) - + self._cache = { "channels": channels_data, "mixes": mixes_data, "timestamp": time.time() } self._save_cache() - + print(f"✅ Cache aggiornata con {len(channels_data)} canali e {len(mixes_data)} mix") return { "status": "success", @@ -339,14 +358,14 @@ class TF5MixerController: "channels_count": len(channels_data), "mixes_count": len(mixes_data) } - + def recall_scene(self, bank: str, scene_number: int) -> dict: """Richiama una scena dal banco A o B. - + Args: bank: Il banco della scena ('a' o 'b') scene_number: Il numero della scena (0-99) - + Returns: Un dizionario con lo stato dell'operazione """ @@ -354,85 +373,85 @@ class TF5MixerController: return {"status": "error", "message": "Il banco deve essere 'a' o 'b'"} if not 0 <= scene_number <= 99: return {"status": "error", "message": "Il numero scena deve essere tra 0 e 99"} - + command = f"ssrecall_ex scene_{bank.lower()} {scene_number}" response = self._send_command(command) - + # Invalida la cache dopo il cambio scena self._cache["timestamp"] = 0 - + return { "status": "success" if "OK" in response else "error", "message": f"Scena {bank.upper()}{scene_number} richiamata. Cache invalidata.", "response": response } - + def set_channel_level(self, channel: int, level_db: float) -> dict: """Imposta il livello del fader di un canale in dB. - + Args: channel: Numero del canale (1-40) level_db: Livello in dB (da -inf a +10.0) - + Returns: Un dizionario con lo stato dell'operazione """ if not 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} - + # Converti dB in valore interno (moltiplicato per 100) if level_db <= -138: internal_value = -32768 # -inf else: internal_value = int(level_db * 100) - + command = f"set MIXER:Current/InCh/Fader/Level {channel-1} 0 {internal_value}" response = self._send_command(command) - + # Aggiorna cache dal mixer per avere dati precisi if "OK" in response: self._update_channel_cache(channel) - + return { "status": "success" if "OK" in response else "error", "message": f"Canale {channel} impostato a {level_db:+.1f} dB", "response": response } - + def set_channel_on_off(self, channel: int, state: bool) -> dict: """Accende o spegne un canale. - + Args: channel: Numero del canale (1-40) state: True per accendere, False per spegnere - + Returns: Un dizionario con lo stato dell'operazione """ if not 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} - + value = 1 if state else 0 command = f"set MIXER:Current/InCh/Fader/On {channel-1} 0 {value}" response = self._send_command(command) - + # Aggiorna cache dal mixer per avere dati precisi if "OK" in response: self._update_channel_cache(channel) - + return { "status": "success" if "OK" in response else "error", "message": f"Canale {channel} {'acceso' if state else 'spento'}", "response": response } - + def set_channel_pan(self, channel: int, pan_value: int) -> dict: """Imposta il pan di un canale. - + Args: channel: Numero del canale (1-40) pan_value: Valore pan da -63 (sinistra) a +63 (destra), 0 è centro - + Returns: Un dizionario con lo stato dell'operazione """ @@ -440,90 +459,90 @@ class TF5MixerController: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} if not -63 <= pan_value <= 63: return {"status": "error", "message": "Il pan deve essere tra -63 e +63"} - + command = f"set MIXER:Current/InCh/ToSt/Pan {channel-1} 0 {pan_value}" response = self._send_command(command) - + # Aggiorna cache dal mixer per avere dati precisi if "OK" in response: self._update_channel_cache(channel) - + pan_desc = "centro" if pan_value < 0: pan_desc = f"sinistra {abs(pan_value)}" elif pan_value > 0: pan_desc = f"destra {pan_value}" - + return { "status": "success" if "OK" in response else "error", "message": f"Canale {channel} pan impostato a {pan_desc}", "response": response } - + def set_mix_level(self, mix_number: int, level_db: float) -> dict: """Imposta il livello di un mix/aux. - + Args: mix_number: Numero del mix (1-20) level_db: Livello in dB (da -inf a +10.0) - + Returns: Un dizionario con lo stato dell'operazione """ if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + if level_db <= -138: internal_value = -32768 else: internal_value = int(level_db * 100) - + command = f"set MIXER:Current/Mix/Fader/Level {mix_number-1} 0 {internal_value}" response = self._send_command(command) - + # Aggiorna cache dal mixer per avere dati precisi if "OK" in response: self._update_mix_cache(mix_number) - + return { "status": "success" if "OK" in response else "error", "message": f"Mix {mix_number} impostato a {level_db:+.1f} dB", "response": response } - + def set_mix_on_off(self, mix_number: int, state: bool) -> dict: """Accende o spegne un mix/aux. - + Args: mix_number: Numero del mix (1-20) state: True per accendere, False per spegnere - + Returns: Un dizionario con lo stato dell'operazione """ if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + value = 1 if state else 0 command = f"set MIXER:Current/Mix/Fader/On {mix_number-1} 0 {value}" response = self._send_command(command) - + # Aggiorna cache dal mixer per avere dati precisi if "OK" in response: self._update_mix_cache(mix_number) - + return { "status": "success" if "OK" in response else "error", "message": f"Mix {mix_number} {'acceso' if state else 'spento'}", "response": response } - + def mute_multiple_channels(self, channels: List[int]) -> dict: """Muta più canali contemporaneamente. - + Args: channels: Lista di numeri di canale da mutare (es: [1, 2, 5, 8]) - + Returns: Un dizionario con lo stato dell'operazione """ @@ -531,20 +550,20 @@ class TF5MixerController: for ch in channels: result = self.set_channel_on_off(ch, False) results.append(result) - + success_count = sum(1 for r in results if r["status"] == "success") return { "status": "success" if success_count == len(channels) else "partial", "message": f"Mutati {success_count}/{len(channels)} canali: {channels}", "details": results } - + def unmute_multiple_channels(self, channels: List[int]) -> dict: """Riattiva più canali contemporaneamente. - + Args: channels: Lista di numeri di canale da riattivare (es: [1, 2, 5, 8]) - + Returns: Un dizionario con lo stato dell'operazione """ @@ -552,53 +571,54 @@ class TF5MixerController: for ch in channels: result = self.set_channel_on_off(ch, True) results.append(result) - + success_count = sum(1 for r in results if r["status"] == "success") return { "status": "success" if success_count == len(channels) else "partial", "message": f"Riattivati {success_count}/{len(channels)} canali: {channels}", "details": results } - + def get_channel_info(self, channel: int, force_refresh: bool = False) -> dict: """Legge le informazioni di un canale (nome, livello, stato, pan). Usa la cache se disponibile e valida. - + Args: channel: Numero del canale (1-40) force_refresh: Se True, ignora la cache e legge dal mixer - + Returns: Un dizionario con tutte le informazioni del canale """ if not 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} - + # Usa cache se valida e non forzato il refresh if not force_refresh and self._is_cache_valid(): cached_data = self._cache.get("channels", {}).get(str(channel)) if cached_data: # Normalizza level_db (None -> -inf) level_db = self._normalize_level_from_cache(cached_data.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) return { "status": "success", "source": "cache", "channel": cached_data["channel"], "name": cached_data["name"], "on": cached_data["on"], - "level_db": level_db, + "level_db": sanitized_level_db, "pan": cached_data.get("pan") } - + # Altrimenti leggi dal mixer ch_idx = channel - 1 - + resp_name = self._send_command(f"get MIXER:Current/InCh/Label/Name {ch_idx} 0") name = self._parse_name(resp_name) - + resp_on = self._send_command(f"get MIXER:Current/InCh/Fader/On {ch_idx} 0") is_on = self._parse_value(resp_on) == "1" - + resp_level = self._send_command(f"get MIXER:Current/InCh/Fader/Level {ch_idx} 0") level_raw = self._parse_value(resp_level) try: @@ -606,62 +626,64 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + resp_pan = self._send_command(f"get MIXER:Current/InCh/ToSt/Pan {ch_idx} 0") pan_raw = self._parse_value(resp_pan) try: pan_value = int(pan_raw) except: pan_value = None - + + sanitized_level_db = self._sanitize_value(level_db) return { "status": "success", "source": "mixer", "channel": channel, "name": name, "on": is_on, - "level_db": level_db, + "level_db": sanitized_level_db, "pan": pan_value } - + def get_mix_info(self, mix_number: int, force_refresh: bool = False) -> dict: """Legge le informazioni di un mix/aux (nome, livello, stato). Usa la cache se disponibile e valida. - + Args: mix_number: Numero del mix (1-20) force_refresh: Se True, ignora la cache e legge dal mixer - + Returns: Un dizionario con tutte le informazioni del mix """ if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + # Usa cache se valida e non forzato il refresh if not force_refresh and self._is_cache_valid(): cached_data = self._cache.get("mixes", {}).get(str(mix_number)) if cached_data: # Normalizza level_db (None -> -inf) level_db = self._normalize_level_from_cache(cached_data.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) return { "status": "success", "source": "cache", "mix": cached_data["mix"], "name": cached_data["name"], "on": cached_data["on"], - "level_db": level_db + "level_db": sanitized_level_db } - + # Altrimenti leggi dal mixer mix_idx = mix_number - 1 - + resp_name = self._send_command(f"get MIXER:Current/Mix/Label/Name {mix_idx} 0") name = self._parse_name(resp_name) - + resp_on = self._send_command(f"get MIXER:Current/Mix/Fader/On {mix_idx} 0") is_on = self._parse_value(resp_on) == "1" - + resp_level = self._send_command(f"get MIXER:Current/Mix/Fader/Level {mix_idx} 0") level_raw = self._parse_value(resp_level) try: @@ -669,47 +691,49 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + + sanitized_level_db = self._sanitize_value(level_db) return { "status": "success", "source": "mixer", "mix": mix_number, "name": name, "on": is_on, - "level_db": level_db + "level_db": sanitized_level_db } - + def search_channels_by_name(self, search_term: str) -> dict: """Cerca canali il cui nome contiene un determinato termine. Usa la cache per velocizzare la ricerca. - + Args: search_term: Il termine da cercare nei nomi dei canali (case-insensitive) - + Returns: Un dizionario con la lista dei canali trovati """ # Aggiorna cache se non valida if not self._is_cache_valid(): self.refresh_cache() - + search_lower = search_term.lower() found_channels = [] - + for ch_str, info in self._cache.get("channels", {}).items(): if search_lower in info.get("name", "").lower(): # Normalizza level_db quando restituisci i risultati level_db = self._normalize_level_from_cache(info.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) found_channels.append({ "channel": info["channel"], "name": info["name"], "on": info["on"], - "level_db": level_db + "level_db": sanitized_level_db }) - + # Ordina per numero canale found_channels.sort(key=lambda x: x["channel"]) - + return { "status": "success", "search_term": search_term, @@ -717,38 +741,39 @@ class TF5MixerController: "channels": found_channels, "message": f"Trovati {len(found_channels)} canali contenenti '{search_term}'" } - + def search_mixes_by_name(self, search_term: str) -> dict: """Cerca mix/aux il cui nome contiene un determinato termine. Usa la cache per velocizzare la ricerca. - + Args: search_term: Il termine da cercare nei nomi dei mix (case-insensitive) - + Returns: Un dizionario con la lista dei mix trovati """ # Aggiorna cache se non valida if not self._is_cache_valid(): self.refresh_cache() - + search_lower = search_term.lower() found_mixes = [] - + for mix_str, info in self._cache.get("mixes", {}).items(): if search_lower in info.get("name", "").lower(): # Normalizza level_db quando restituisci i risultati level_db = self._normalize_level_from_cache(info.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) found_mixes.append({ "mix": info["mix"], "name": info["name"], "on": info["on"], - "level_db": level_db + "level_db": sanitized_level_db }) - + # Ordina per numero mix found_mixes.sort(key=lambda x: x["mix"]) - + return { "status": "success", "search_term": search_term, @@ -756,31 +781,34 @@ class TF5MixerController: "mixes": found_mixes, "message": f"Trovati {len(found_mixes)} mix contenenti '{search_term}'" } - + def get_all_channels_summary(self) -> dict: """Ottiene un riepilogo di tutti i canali con nome e stato. Usa la cache per velocizzare. - + Returns: Un dizionario con il riepilogo di tutti i canali """ # Aggiorna cache se non valida if not self._is_cache_valid(): self.refresh_cache() - + channels = [] for ch_str, info in self._cache.get("channels", {}).items(): + level_db = self._normalize_level_from_cache(info.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) channels.append({ "channel": info["channel"], "name": info["name"], - "on": info["on"] + "on": info["on"], + "level_db": sanitized_level_db }) - + # Ordina per numero canale channels.sort(key=lambda x: x["channel"]) - + cache_age = time.time() - self._cache.get("timestamp", 0) - + return { "status": "success", "total_channels": len(channels), @@ -788,31 +816,34 @@ class TF5MixerController: "cache_age_seconds": int(cache_age), "message": f"Riepilogo di {len(channels)} canali (cache: {int(cache_age)}s fa)" } - + def get_all_mixes_summary(self) -> dict: """Ottiene un riepilogo di tutti i mix/aux con nome e stato. Usa la cache per velocizzare. - + Returns: Un dizionario con il riepilogo di tutti i mix """ # Aggiorna cache se non valida if not self._is_cache_valid(): self.refresh_cache() - + mixes = [] for mix_str, info in self._cache.get("mixes", {}).items(): + level_db = self._normalize_level_from_cache(info.get("level_db")) + sanitized_level_db = self._sanitize_value(level_db) mixes.append({ "mix": info["mix"], "name": info["name"], - "on": info["on"] + "on": info["on"], + "level_db": sanitized_level_db }) - + # Ordina per numero mix mixes.sort(key=lambda x: x["mix"]) - + cache_age = time.time() - self._cache.get("timestamp", 0) - + return { "status": "success", "total_mixes": len(mixes), @@ -823,12 +854,12 @@ class TF5MixerController: def set_channel_to_mix_level(self, channel: int, mix_number: int, level_db: float) -> dict: """Imposta il livello di invio di un canale verso un mix/aux. - + Args: channel: Numero del canale (1-40) mix_number: Numero del mix (1-20) level_db: Livello in dB (da -inf a +10.0) - + Returns: Un dizionario con lo stato dell'operazione """ @@ -836,15 +867,15 @@ class TF5MixerController: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + if level_db <= -138: internal_value = -32768 else: internal_value = int(level_db * 100) - + command = f"set MIXER:Current/InCh/ToMix/Level {channel-1} {mix_number-1} {internal_value}" response = self._send_command(command) - + return { "status": "success" if "OK" in response else "error", "message": f"Canale {channel} → Mix {mix_number} impostato a {level_db:+.1f} dB", @@ -853,12 +884,12 @@ class TF5MixerController: def set_channel_to_mix_on_off(self, channel: int, mix_number: int, state: bool) -> dict: """Accende o spegne l'invio di un canale verso un mix/aux. - + Args: channel: Numero del canale (1-40) mix_number: Numero del mix (1-20) state: True per accendere, False per spegnere - + Returns: Un dizionario con lo stato dell'operazione """ @@ -866,11 +897,11 @@ class TF5MixerController: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + value = 1 if state else 0 command = f"set MIXER:Current/InCh/ToMix/On {channel-1} {mix_number-1} {value}" response = self._send_command(command) - + return { "status": "success" if "OK" in response else "error", "message": f"Invio canale {channel} → Mix {mix_number} {'acceso' if state else 'spento'}", @@ -879,11 +910,11 @@ class TF5MixerController: def get_channel_to_mix_info(self, channel: int, mix_number: int) -> dict: """Legge le informazioni dell'invio di un canale verso un mix. - + Args: channel: Numero del canale (1-40) mix_number: Numero del mix (1-20) - + Returns: Un dizionario con le informazioni del send """ @@ -891,10 +922,10 @@ class TF5MixerController: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - + ch_idx = channel - 1 mix_idx = mix_number - 1 - + # Leggi livello resp_level = self._send_command(f"get MIXER:Current/InCh/ToMix/Level {ch_idx} {mix_idx}") level_raw = self._parse_value(resp_level) @@ -903,16 +934,18 @@ class TF5MixerController: level_db = level_int / 100.0 if level_int > -32768 else float('-inf') except: level_db = None - + # Leggi stato ON/OFF resp_on = self._send_command(f"get MIXER:Current/InCh/ToMix/On {ch_idx} {mix_idx}") is_on = self._parse_value(resp_on) == "1" - + + sanitized_level_db = self._sanitize_value(level_db) + return { "status": "success", "channel": channel, "mix": mix_number, "on": is_on, - "level_db": level_db, - "message": f"Canale {channel} → Mix {mix_number}: {level_db:+.1f} dB ({'ON' if is_on else 'OFF'})" - } \ No newline at end of file + "send_level_db": sanitized_level_db, + "message": f"Canale {channel} → Mix {mix_number}: {sanitized_level_db:+.1f} dB ({'ON' if is_on else 'OFF'})" + }