diff --git a/.tf5_mixer_cache/channels_cache.json b/.tf5_mixer_cache/channels_cache.json index 803ccc3..2a443d7 100644 --- a/.tf5_mixer_cache/channels_cache.json +++ b/.tf5_mixer_cache/channels_cache.json @@ -403,5 +403,5 @@ "level_db": 0.01 } }, - "timestamp": 1762198236.5701377 + "timestamp": 1762800542.9757586 } \ No newline at end of file diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc index 674be36..3ccb7fe 100644 Binary files a/__pycache__/config.cpython-312.pyc and b/__pycache__/config.cpython-312.pyc differ diff --git a/__pycache__/mixer_controller.cpython-312.pyc b/__pycache__/mixer_controller.cpython-312.pyc index de7bc6a..866673a 100644 Binary files a/__pycache__/mixer_controller.cpython-312.pyc and b/__pycache__/mixer_controller.cpython-312.pyc differ diff --git a/cache/mixer_state.json b/cache/mixer_state.json new file mode 100644 index 0000000..34212fa --- /dev/null +++ b/cache/mixer_state.json @@ -0,0 +1,407 @@ +{ + "channels": { + "1": { + "channel": 1, + "name": "Sconosciuto", + "on": false, + "level_db": 0.65, + "pan": 0 + }, + "2": { + "channel": 2, + "name": "Gelato", + "on": true, + "level_db": null, + "pan": 0 + }, + "3": { + "channel": 3, + "name": "Talkback", + "on": false, + "level_db": null, + "pan": 0 + }, + "4": { + "channel": 4, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "5": { + "channel": 5, + "name": "Vox1", + "on": true, + "level_db": -13.15, + "pan": 0 + }, + "6": { + "channel": 6, + "name": "Vox2", + "on": true, + "level_db": null, + "pan": 0 + }, + "7": { + "channel": 7, + "name": "Basso", + "on": false, + "level_db": null, + "pan": 0 + }, + "8": { + "channel": 8, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "9": { + "channel": 9, + "name": "Kick", + "on": true, + "level_db": -5.2, + "pan": 0 + }, + "10": { + "channel": 10, + "name": "Snare", + "on": true, + "level_db": -9.95, + "pan": 0 + }, + "11": { + "channel": 11, + "name": "Tom 1", + "on": true, + "level_db": -16.6, + "pan": 0 + }, + "12": { + "channel": 12, + "name": "Tom 2", + "on": true, + "level_db": -3.45, + "pan": 0 + }, + "13": { + "channel": 13, + "name": "Tom3", + "on": true, + "level_db": -24.0, + "pan": 0 + }, + "14": { + "channel": 14, + "name": "Pan SX", + "on": true, + "level_db": -15.08, + "pan": 0 + }, + "15": { + "channel": 15, + "name": "Pan dx", + "on": true, + "level_db": -14.68, + "pan": 0 + }, + "16": { + "channel": 16, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "17": { + "channel": 17, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "18": { + "channel": 18, + "name": "Archetto", + "on": false, + "level_db": null, + "pan": 0 + }, + "19": { + "channel": 19, + "name": "Vox 5", + "on": false, + "level_db": null, + "pan": 0 + }, + "20": { + "channel": 20, + "name": "Tast", + "on": true, + "level_db": -10.8, + "pan": 0 + }, + "21": { + "channel": 21, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "22": { + "channel": 22, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "23": { + "channel": 23, + "name": " Vox3", + "on": true, + "level_db": null, + "pan": 0 + }, + "24": { + "channel": 24, + "name": "Chit cnt", + "on": true, + "level_db": -0.5, + "pan": 0 + }, + "25": { + "channel": 25, + "name": "Chit dx", + "on": true, + "level_db": -22.7, + "pan": 0 + }, + "26": { + "channel": 26, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "27": { + "channel": 27, + "name": "Vox 4", + "on": false, + "level_db": null, + "pan": 0 + }, + "28": { + "channel": 28, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "29": { + "channel": 29, + "name": "Pad", + "on": false, + "level_db": null, + "pan": 0 + }, + "30": { + "channel": 30, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "31": { + "channel": 31, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "32": { + "channel": 32, + "name": "Sconosciuto", + "on": false, + "level_db": null, + "pan": 0 + }, + "33": { + "channel": 33, + "name": "PC", + "on": true, + "level_db": -17.5, + "pan": -63 + }, + "34": { + "channel": 34, + "name": "PC", + "on": true, + "level_db": -17.5, + "pan": 63 + }, + "35": { + "channel": 35, + "name": "ch35", + "on": true, + "level_db": null, + "pan": 0 + }, + "36": { + "channel": 36, + "name": "ch36", + "on": true, + "level_db": null, + "pan": 0 + }, + "37": { + "channel": 37, + "name": "ch37", + "on": true, + "level_db": null, + "pan": 0 + }, + "38": { + "channel": 38, + "name": "ch38", + "on": true, + "level_db": null, + "pan": 0 + }, + "39": { + "channel": 39, + "name": "ch39", + "on": true, + "level_db": null, + "pan": 0 + }, + "40": { + "channel": 40, + "name": "ch40", + "on": true, + "level_db": null, + "pan": 0 + } + }, + "mixes": { + "1": { + "mix": 1, + "name": "Aux 1", + "on": true, + "level_db": -1.55 + }, + "2": { + "mix": 2, + "name": "Aux 2", + "on": true, + "level_db": -0.05 + }, + "3": { + "mix": 3, + "name": "Aux 3", + "on": true, + "level_db": -0.5 + }, + "4": { + "mix": 4, + "name": "Aux 4", + "on": true, + "level_db": -0.6 + }, + "5": { + "mix": 5, + "name": "batteria", + "on": true, + "level_db": 1.35 + }, + "6": { + "mix": 6, + "name": "Aux 6", + "on": true, + "level_db": -6.65 + }, + "7": { + "mix": 7, + "name": "Aux 7", + "on": true, + "level_db": -2.7 + }, + "8": { + "mix": 8, + "name": "Aux 8", + "on": true, + "level_db": -2.65 + }, + "9": { + "mix": 9, + "name": "Aux 9/10", + "on": true, + "level_db": -8.1 + }, + "10": { + "mix": 10, + "name": "Aux 9/10", + "on": true, + "level_db": -8.1 + }, + "11": { + "mix": 11, + "name": "Aux Pulp", + "on": true, + "level_db": -10.3 + }, + "12": { + "mix": 12, + "name": "Aux Pulp", + "on": true, + "level_db": -10.3 + }, + "13": { + "mix": 13, + "name": "Aux13/14", + "on": false, + "level_db": -1.6 + }, + "14": { + "mix": 14, + "name": "Aux13/14", + "on": false, + "level_db": -1.6 + }, + "15": { + "mix": 15, + "name": "Aux15/16", + "on": true, + "level_db": 0.1 + }, + "16": { + "mix": 16, + "name": "Aux15/16", + "on": true, + "level_db": 0.1 + }, + "17": { + "mix": 17, + "name": "Diretta", + "on": true, + "level_db": 0.0 + }, + "18": { + "mix": 18, + "name": "Diretta", + "on": true, + "level_db": 0.0 + }, + "19": { + "mix": 19, + "name": "Traduzio", + "on": true, + "level_db": 6.0 + }, + "20": { + "mix": 20, + "name": "Traduzio", + "on": true, + "level_db": 6.0 + } + }, + "timestamp": 1762800269.2657 +} \ No newline at end of file diff --git a/config.py b/config.py index 2271daa..9050c08 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,5 @@ # Configurazione mixer -DEFAULT_HOST = "192.168.1.62" +DEFAULT_HOST = "192.168.1.64" DEFAULT_PORT = 49280 TF5_INPUT_CHANNELS = 40 TF5_MIX_BUSSES = 20 diff --git a/conversations/309700430.json b/conversations/309700430.json new file mode 100644 index 0000000..9ad3925 --- /dev/null +++ b/conversations/309700430.json @@ -0,0 +1,93 @@ +{ + "user_id": 309700430, + "username": "Cicciomt7", + "last_updated": 1762198991.2953947, + "last_updated_str": "2025-11-03 19:43:11", + "message_count": 14, + "history": [ + { + "role": "user", + "content": "Chiudi vox 5", + "timestamp": 1762198915.897361, + "timestamp_str": "2025-11-03 19:41:55" + }, + { + "role": "assistant", + "content": "Ok, Vox 5 (canale 19) è spento. 🎤❌", + "timestamp": 1762198920.8823922, + "timestamp_str": "2025-11-03 19:42:00" + }, + { + "role": "user", + "content": "Apri vox 5", + "timestamp": 1762198929.1841967, + "timestamp_str": "2025-11-03 19:42:09" + }, + { + "role": "assistant", + "content": "Ok, Vox 5 (canale 19) è acceso. 🎤✅", + "timestamp": 1762198931.5372686, + "timestamp_str": "2025-11-03 19:42:11" + }, + { + "role": "user", + "content": "Chiusi vox 3", + "timestamp": 1762198935.3993735, + "timestamp_str": "2025-11-03 19:42:15" + }, + { + "role": "assistant", + "content": "Non ho trovato nessun canale chiamato \"vox 3\". Sei sicuro del nome? Forse intendevi un altro canale o un numero?", + "timestamp": 1762198938.0440562, + "timestamp_str": "2025-11-03 19:42:18" + }, + { + "role": "user", + "content": "Vox3", + "timestamp": 1762198948.152694, + "timestamp_str": "2025-11-03 19:42:28" + }, + { + "role": "assistant", + "content": "Ok, \" Vox3\" (canale 23) è spento. 🎤❌", + "timestamp": 1762198953.3262546, + "timestamp_str": "2025-11-03 19:42:33" + }, + { + "role": "user", + "content": "Apri vox3", + "timestamp": 1762198964.16481, + "timestamp_str": "2025-11-03 19:42:44" + }, + { + "role": "assistant", + "content": "Ok, Vox3 (canale 23) è acceso. 🎤✅", + "timestamp": 1762198966.712192, + "timestamp_str": "2025-11-03 19:42:46" + }, + { + "role": "user", + "content": "Chiudi il gelato", + "timestamp": 1762198972.835868, + "timestamp_str": "2025-11-03 19:42:52" + }, + { + "role": "assistant", + "content": "Ok, \"Gelato\" (canale 2) è spento. 🎤❌", + "timestamp": 1762198975.563454, + "timestamp_str": "2025-11-03 19:42:55" + }, + { + "role": "user", + "content": "Riaprilo", + "timestamp": 1762198989.0117595, + "timestamp_str": "2025-11-03 19:43:09" + }, + { + "role": "assistant", + "content": "Ok, \"Gelato\" (canale 2) è acceso. 🎤✅", + "timestamp": 1762198991.295375, + "timestamp_str": "2025-11-03 19:43:11" + } + ] +} \ No newline at end of file diff --git a/conversations/436176546.json b/conversations/436176546.json new file mode 100644 index 0000000..4bb98e7 --- /dev/null +++ b/conversations/436176546.json @@ -0,0 +1,21 @@ +{ + "user_id": 436176546, + "username": "Gloriushhh", + "last_updated": 1762199858.7119617, + "last_updated_str": "2025-11-03 19:57:38", + "message_count": 2, + "history": [ + { + "role": "user", + "content": "Alza il volume del mio canale", + "timestamp": 1762199853.7008705, + "timestamp_str": "2025-11-03 19:57:33" + }, + { + "role": "assistant", + "content": "Non riesco a trovare il tuo canale 'VOX 2'. Sei sicuro che si chiami così? Potrebbe esserci un nome leggermente diverso o il canale non è presente.", + "timestamp": 1762199858.7119193, + "timestamp_str": "2025-11-03 19:57:38" + } + ] +} \ No newline at end of file diff --git a/conversations/4762864.json b/conversations/4762864.json index ee6f7b6..2db55f2 100644 --- a/conversations/4762864.json +++ b/conversations/4762864.json @@ -1,135 +1,135 @@ { "user_id": 4762864, "username": "unnikked", - "last_updated": 1762198281.0663533, - "last_updated_str": "2025-11-03 20:31:21", + "last_updated": 1762800926.9208193, + "last_updated_str": "2025-11-10 19:55:26", "message_count": 21, "history": [ { "role": "assistant", - "content": "Ciao! Certo, posso alzarti la chitarra nel tuo mix in cuffia.\n\nPrima di tutto, quale chitarra intendi? C'è \"Chit cnt\" (chitarra centrale) e \"Chit dx\" (chitarra destra) nel mix della diretta.\n\nE poi, per favore, dimmi qual è il numero del tuo mix in-ear o del tuo monitor, così so dove alzare il volume.", - "timestamp": 1762197501.4060967, - "timestamp_str": "2025-11-03 19:18:21" + "content": "Perfetto, Nicola! Ho forzato l'aggiornamento e ora vedo il valore corretto.\n\nIl microfono \"Gelato\" (Canale 2) è ora a -30.5 dB e risulta acceso. ✅", + "timestamp": 1762799608.2462869, + "timestamp_str": "2025-11-10 19:33:28" }, { "role": "user", - "content": "Cnt", - "timestamp": 1762197513.5233943, - "timestamp_str": "2025-11-03 19:18:33" + "content": "E ora?", + "timestamp": 1762799621.5972867, + "timestamp_str": "2025-11-10 19:33:41" }, { "role": "assistant", - "content": "Ok, Nicola, intendi la \"Chit cnt\" (chitarra centrale).\n\nPerò, ho bisogno di sapere qual è il numero del tuo mix in-ear o del tuo monitor. Così posso alzare la chitarra nel mix giusto per te! Qual è il tuo mix?", - "timestamp": 1762197515.409458, - "timestamp_str": "2025-11-03 19:18:35" + "content": "Ok, Nicola! Ora che sappiamo che il microfono \"Gelato\" (Canale 2) è a -30.5 dB e acceso, cosa vuoi fare?\n\nVuoi:\n* Alzarlo o abbassarlo?\n* Accenderlo o spegnerlo?\n* Cambiare il bilanciamento (pan)?\n* Mandarlo a qualche mix in particolare?\n\nDimmi tu! 🎤🔊", + "timestamp": 1762799624.0316906, + "timestamp_str": "2025-11-10 19:33:44" }, { "role": "user", - "content": "Te l'ho già detto aux2", - "timestamp": 1762197528.5552773, - "timestamp_str": "2025-11-03 19:18:48" + "content": "Com'è ora?", + "timestamp": 1762799994.9021049, + "timestamp_str": "2025-11-10 19:39:54" }, { "role": "assistant", - "content": "Fatto, Nicola! Ho alzato la \"Chit cnt\" nel tuo Aux 2 a +5.6 dB. Dovresti sentirla più forte ora! ✅", - "timestamp": 1762197538.5911577, - "timestamp_str": "2025-11-03 19:18:58" + "content": "Nicola, ho ricontrollato il microfono \"Gelato\" (Canale 2) e, strano ma vero, il suo volume è tornato a -120 dB. Risulta ancora acceso, ma il livello è bassissimo, praticamente muto.\n\nEri sicuro di averlo modificato prima? Sembra che il mixer non abbia mantenuto il cambiamento, o che sia stato riportato a quel valore. 🤔", + "timestamp": 1762800000.7192562, + "timestamp_str": "2025-11-10 19:40:00" }, { "role": "user", - "content": "Alzami il gelato in cuffia", - "timestamp": 1762198086.6308124, - "timestamp_str": "2025-11-03 20:28:06" + "content": "Controlla ora", + "timestamp": 1762800011.8181467, + "timestamp_str": "2025-11-10 19:40:11" }, { "role": "assistant", - "content": "Fatto, Nicola! Ho alzato il \"Gelato\" nel tuo Aux 2 a +5.0 dB. Dovresti sentirlo più forte ora! 🍦🔊", - "timestamp": 1762198096.3878605, - "timestamp_str": "2025-11-03 20:28:16" + "content": "Ok, Nicola! Controllo subito il microfono \"Gelato\" (Canale 2) e forzo l'aggiornamento della cache per essere sicuro.", + "timestamp": 1762800013.4262505, + "timestamp_str": "2025-11-10 19:40:13" }, { "role": "user", - "content": "Perché hai fatto aux2?", - "timestamp": 1762198124.3196917, - "timestamp_str": "2025-11-03 20:28:44" + "content": "Non c'è bisogno ho fixato il core interno, si sincronizza da solo", + "timestamp": 1762800034.1495132, + "timestamp_str": "2025-11-10 19:40:34" }, { "role": "assistant", - "content": "Hai ragione a chiedere, Nicola! Scusa la confusione.\n\nIl tuo profilo utente dice che il tuo mix assegnato è 'Aux3'. Però, quando mi hai chiesto \"Mi alzi la chitarra in cuffia?\" e ti ho chiesto il numero del tuo mix, tu mi hai risposto \"Te l'ho già detto aux2\".\n\nHo usato 'Aux2' perché me lo hai detto tu esplicitamente.\n\nQual è quello giusto per te? 'Aux2' o 'Aux3'? Voglio essere sicuro di fare le modifiche nel mix corretto!", - "timestamp": 1762198127.4780977, - "timestamp_str": "2025-11-03 20:28:47" + "content": "Ok, Nicola! Perfetto, se il core interno è a posto e si sincronizza da solo, allora non serve forzare la cache.\n\nControllo subito il microfono \"Gelato\" (Canale 2) per vedere com'è ora.", + "timestamp": 1762800036.1529114, + "timestamp_str": "2025-11-10 19:40:36" }, { "role": "user", - "content": "Fai fede alle info del profilo", - "timestamp": 1762198147.697439, - "timestamp_str": "2025-11-03 20:29:07" + "content": "Vai controlla", + "timestamp": 1762800043.7088287, + "timestamp_str": "2025-11-10 19:40:43" }, { "role": "assistant", - "content": "Capito, Nicola! Chiedo scusa per l'errore, hai perfettamente ragione. Farò fede alle informazioni del tuo profilo d'ora in poi.\n\nIl tuo mix assegnato è **'Aux3'**. Userò sempre quello per le tue richieste \"in cuffia\" o \"nel mio monitor\".\n\nHo usato 'Aux2' prima perché me lo avevi indicato tu, ma ora è chiaro che devo attenermi al tuo profilo. Grazie per la precisazione!\n\nC'è qualcos'altro che posso fare per te, sempre tenendo conto che il tuo mix è l'Aux3?", - "timestamp": 1762198151.41241, - "timestamp_str": "2025-11-03 20:29:11" + "content": "Nicola, ho ricontrollato il microfono \"Gelato\" (Canale 2).\n\nIl suo volume è ancora a -120 dB e risulta acceso. Sembra che il cambiamento non sia stato mantenuto o che sia tornato a questo valore. 🤔", + "timestamp": 1762800046.0495343, + "timestamp_str": "2025-11-10 19:40:46" }, { "role": "user", - "content": "Chiudi il gelato", - "timestamp": 1762198170.6018848, - "timestamp_str": "2025-11-03 20:29:30" + "content": "Ora?", + "timestamp": 1762800057.1947744, + "timestamp_str": "2025-11-10 19:40:57" }, { "role": "assistant", - "content": "Ah, scusa Nicola! Sembra che il nome \"Aux3\" non sia esattamente come è registrato nel mixer, o forse la cache non lo ha ancora. Però, dato che il tuo profilo dice \"Aux3\", userò il **Mix 3** come riferimento, che è la cosa più logica.\n\nHo spento l'invio del canale \"Gelato\" (Canale 2) al tuo Mix 3. Non dovresti più sentirlo in cuffia. 🍦❌", - "timestamp": 1762198178.0147443, - "timestamp_str": "2025-11-03 20:29:38" + "content": "Nicola, ho ricontrollato il microfono \"Gelato\" (Canale 2).\n\nIl suo volume è ancora a -120 dB e risulta acceso. Sembra che il cambiamento non sia stato mantenuto o che sia tornato a questo valore. 🤔", + "timestamp": 1762800061.851726, + "timestamp_str": "2025-11-10 19:41:01" }, { "role": "user", - "content": "Lo vedo ancora aperto", - "timestamp": 1762198194.62413, - "timestamp_str": "2025-11-03 20:29:54" + "content": "Ho risolto tutto controlla ora", + "timestamp": 1762800880.4108913, + "timestamp_str": "2025-11-10 19:54:40" }, { "role": "assistant", - "content": "Ok, Nicola, ho controllato!\n\nSecondo il mixer, l'invio del canale \"Gelato\" (Canale 2) al **Mix 3** è effettivamente **spento** (OFF) e il livello è a -120 dB, che è silenzio totale. 🍦❌\n\nSe lo vedi ancora \"aperto\", potrebbe esserci un piccolo ritardo nel display che stai guardando, oppure stiamo parlando di un mix diverso.\n\nSe il tuo monitor in cuffia non è il Mix 3, qual è il numero esatto del tuo mix? Oppure, stai guardando il fader principale del canale \"Gelato\" e non il suo invio al tuo mix?", - "timestamp": 1762198206.2512093, - "timestamp_str": "2025-11-03 20:30:06" + "content": "Ok, Nicola! Perfetto, se hai risolto tutto e si sincronizza da solo, controllo subito il microfono \"Gelato\" (Canale 2) per vedere com'è ora.", + "timestamp": 1762800881.9912424, + "timestamp_str": "2025-11-10 19:54:41" }, { "role": "user", - "content": "Refresh la cache e ricontrolla", - "timestamp": 1762198230.5704525, - "timestamp_str": "2025-11-03 20:30:30" + "content": "Si vai", + "timestamp": 1762800887.5953403, + "timestamp_str": "2025-11-10 19:54:47" }, { "role": "assistant", - "content": "Ok, Nicola, ho aggiornato la cache e ricontrollato.\n\nIl canale \"Gelato\" (Canale 2) è **spento** (OFF) per l'invio al tuo Mix 3, anche se il livello di send è impostato a +6.0 dB. Questo significa che, anche se il fader di send è alto, il segnale non sta passando perché l'invio è disattivato.\n\nSe lo vedi ancora \"aperto\" sul tuo display, potrebbe essere un'indicazione visiva del livello impostato, ma l'audio non dovrebbe arrivare al tuo Mix 3.\n\nVuoi che lo accenda, così il segnale arriva?", - "timestamp": 1762198239.2318952, - "timestamp_str": "2025-11-03 20:30:39" + "content": "Nicola, ho ricontrollato il microfono \"Gelato\" (Canale 2).\n\nOra il suo volume è a **-42.6 dB** e risulta acceso. Il bilanciamento (pan) è a -225, che è un valore un po' strano, di solito va da -63 a +63.\n\nSembra che il volume sia ancora piuttosto basso, ma almeno non è più a -120 dB! ✅", + "timestamp": 1762800890.5927186, + "timestamp_str": "2025-11-10 19:54:50" }, { "role": "user", - "content": "Accendilo e spegnilo", - "timestamp": 1762198267.3801525, - "timestamp_str": "2025-11-03 20:31:07" + "content": "Ora?", + "timestamp": 1762800907.6851556, + "timestamp_str": "2025-11-10 19:55:07" }, { "role": "assistant", - "content": "Fatto! Ho acceso e poi spento l'invio del \"Gelato\" (Canale 2) al tuo Mix 3. Ora dovrebbe essere definitivamente chiuso. 🍦❌", - "timestamp": 1762198271.9373705, - "timestamp_str": "2025-11-03 20:31:11" + "content": "Nicola, ho ricontrollato il microfono \"Gelato\" (Canale 2).\n\nOttime notizie! Ora il suo volume è a **+10 dB**, quindi al massimo! ✅ Risulta acceso e il bilanciamento (pan) è ancora a -225, che è un valore un po' insolito.\n\nSembra che tu abbia risolto alla grande il problema del volume! 🚀", + "timestamp": 1762800910.5955534, + "timestamp_str": "2025-11-10 19:55:10" }, { "role": "user", - "content": "Esatto bravo", - "timestamp": 1762198279.3314657, - "timestamp_str": "2025-11-03 20:31:19" + "content": "Yeeee", + "timestamp": 1762800924.2944915, + "timestamp_str": "2025-11-10 19:55:24" }, { "role": "assistant", - "content": "Perfetto, Nicola! Sono contento che abbiamo risolto. ✅\n\nC'è qualcos'altro che posso fare per te?", - "timestamp": 1762198281.0663533, - "timestamp_str": "2025-11-03 20:31:21" + "content": "Grande, Nicola! Sono contento che siamo riusciti a sistemare il \"Gelato\"! 🚀\n\nOra che è a +10 dB e bello forte, c'è qualcos'altro che posso fare per te? Fammi sapere! 🎤🔊", + "timestamp": 1762800926.9197168, + "timestamp_str": "2025-11-10 19:55:26" } ] } \ No newline at end of file diff --git a/mixer_controller.py b/mixer_controller.py index d443ade..275ec24 100644 --- a/mixer_controller.py +++ b/mixer_controller.py @@ -1,3 +1,5 @@ +# mixer_controller.py + #!/usr/bin/env python # -*- coding: utf-8 -*- import socket @@ -6,1001 +8,557 @@ import os import json import time import math +import threading +import queue 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() - from config import * - class TF5MixerController: + """ + Controllore per mixer Yamaha TF5 con connessione socket persistente e + gestione dei messaggi NOTIFY per l'aggiornamento della cache in tempo reale. + """ def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT): self.host = host self.port = port self.socket = None self._ensure_cache_dir() + + self._cache_lock = threading.Lock() 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 + self._command_lock = threading.Lock() + self._response_queue = queue.Queue(maxsize=1) + self._reader_thread = None + self._is_running = threading.Event() + self._is_connected = threading.Event() - return value + self.start() - def _connect(self): - """Stabilisce la connessione se non già connesso.""" - if self.socket is None: - print('Inizializzazione socket...') + def start(self): + if self._is_running.is_set(): + print("Controller già in esecuzione.") + return + + print("🚀 Avvio del Mixer Controller...") + self._is_running.set() + self._reader_thread = threading.Thread(target=self._connection_manager) + self._reader_thread.daemon = True + self._reader_thread.start() + + if not self._is_connected.wait(timeout=5): + print("⚠️ Impossibile connettersi al mixer all'avvio.") + else: + print("✅ Controller avviato e connesso.") + + def _connection_manager(self): + while self._is_running.is_set(): try: + print(f"🔌 Tentativo di connessione a {self.host}:{self.port}...") self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.settimeout(5) + self.socket.settimeout(10) self.socket.connect((self.host, self.port)) - except socket.error as e: - self.socket = None - raise ConnectionError(f"Impossibile connettersi al mixer: {e}") + self.socket.settimeout(1) + self._is_connected.set() + print(f"🔗 Connesso a {self.host}:{self.port}") - def _disconnect(self): - """Chiude la connessione.""" - if self.socket: - print('Chiusura socket...') - try: - self.socket.close() - print('Socket chiuso!') - except: - print('Errore durante chiusura socket!') - pass + with self._cache_lock: + is_cache_empty = not self._cache.get("channels") + + if is_cache_empty: + print("Cache iniziale vuota. Avvio refresh completo...") + self.refresh_cache() + + self._socket_reader() + except socket.error as e: + print(f"🔥 Errore di connessione: {e}. Riprovo tra 5 secondi...") finally: + self._is_connected.clear() + if self.socket: + self.socket.close() self.socket = None + if self._is_running.is_set(): + time.sleep(5) + print("🛑 Gestore connessioni terminato.") - 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): + def _socket_reader(self): + buffer = "" + while self._is_running.is_set(): try: - print(f'Tentativo di connessione {attempt} per {command}') - self._connect() - self.socket.sendall((command + '\n').encode('utf-8')) - response = self.socket.recv(4096) - decoded = response.decode('utf-8', errors='ignore').strip() - print(f'Risposta {decoded}') - return decoded + data = self.socket.recv(4096) + if not data: + print("💔 Connessione chiusa dal mixer.") + break + buffer += data.decode('utf-8', errors='ignore') + while '\n' in buffer: + line, buffer = buffer.split('\n', 1) + line = line.strip() + if not line: + continue + + if line.startswith("NOTIFY"): + self._handle_notify(line) + elif line.startswith("OK") or line.startswith("ERROR"): + try: + self._response_queue.put_nowait(line) + except queue.Full: + print(f"⚠️ Coda risposte piena, scartato: {line}") + else: + print(f"🤔 Messaggio non gestito: {line}") + + except socket.timeout: + continue except socket.error as e: - print(f'Errore di connessione dopo {max_retries} tentativi: {e}') - self._disconnect() + print(f"🔥 Errore di lettura dal socket: {e}") + break - if attempt < max_retries - 1: - time.sleep(0.1) - continue + def _handle_notify(self, message: str): + """ + Analizza un messaggio NOTIFY e aggiorna la cache in modo robusto. + Versione che gestisce il formato esatto 'NOTIFY set ...' con parametri extra. + """ + print(f"RECV NOTIFY: {message}") + + parts = message.split() + if len(parts) < 2: return + + path = None + path_index = -1 + for i, part in enumerate(parts): + if part.startswith("MIXER:"): + path = part + path_index = i + break + + if path is None: + print(f" -> ATTENZIONE: Nessun path valido (che inizia con 'MIXER:') trovato nel messaggio.") + return + + print(f" -> Tentativo di acquisire il lock per l'aggiornamento (Path: {path})...") + with self._cache_lock: + print(f" -> LOCK ACQUISITO. Elaborazione...") + try: + updated = False + + # --- LOGICA DI PARSING SPECIFICA E ROBUSTA --- + # Per 'set' commands, i parametri sono: ch_idx, 0, value, [optional_string] + # Indice ch: path_index + 1 + # Indice valore: path_index + 3 + + # Gestione NOME Canale (usa un parsing diverso) + if "InCh/Label/Name" in path: + params = parts[path_index + 1:] + if len(params) >= 2: + ch_idx, name = int(params[0]), " ".join(params[1:]).strip('"') + ch_key = str(ch_idx + 1) + + channel_data = self._cache.setdefault("channels", {}).setdefault(ch_key, {"channel": int(ch_key)}) + channel_data["name"] = name + print(f" -> SUCCESS: Ch {ch_key} nome -> '{name}'") + updated = True + + # Gestione ON/OFF Canale + elif "InCh/Fader/On" in path: + if len(parts) > path_index + 3: + ch_idx = int(parts[path_index + 1]) + state_val = parts[path_index + 3] + state = state_val == "1" + ch_key = str(ch_idx + 1) + + channel_data = self._cache.setdefault("channels", {}).setdefault(ch_key, {"channel": int(ch_key)}) + channel_data["on"] = state + print(f" -> SUCCESS: Ch {ch_key} stato -> {'ON' if state else 'OFF'}") + updated = True + + # Gestione LIVELLO Canale + elif "InCh/Fader/Level" in path: + if len(parts) > path_index + 3: + ch_idx = int(parts[path_index + 1]) + level_int_str = parts[path_index + 3] + level_int = int(level_int_str) + ch_key = str(ch_idx + 1) + level_db = level_int / 100.0 if level_int > -32768 else float('-inf') + + channel_data = self._cache.setdefault("channels", {}).setdefault(ch_key, {"channel": int(ch_key)}) + channel_data["level_db"] = level_db + print(f" -> SUCCESS: Ch {ch_key} level -> {level_db:.2f} dB") + updated = True + + if updated: + self._cache['timestamp'] = time.time() else: - return f"Errore di connessione dopo {max_retries} tentativi: {e}" + print(f" -> ATTENZIONE: Nessuna condizione di aggiornamento trovata per il path '{path}'") + + except (ValueError, IndexError) as e: + print(f" -> ERRORE CRITICO durante l'elaborazione: {e}") + finally: + print(f" -> LOCK RILASCIATO.") + + def _send_command(self, command: str) -> str: + """Invia un comando al mixer e attende la risposta.""" + if not self._is_connected.wait(timeout=5): + return "ERROR connection - Mixer non connesso." + + with self._command_lock: + try: + while not self._response_queue.empty(): + self._response_queue.get_nowait() + + print(f"SEND: {command}") + self.socket.sendall((command + '\n').encode('utf-8')) + + response = self._response_queue.get(timeout=5) + print(f"RECV RESP: {response}") + return response + + except queue.Empty: + return "ERROR timeout - Nessuna risposta dal mixer." + except socket.error as e: + self._is_connected.clear() + return f"ERROR connection - Errore socket: {e}" def close(self): - """Chiude la connessione (da chiamare alla fine).""" - self._disconnect() + """Ferma il controller e chiude la connessione.""" + if not self._is_running.is_set(): + return + print("🛑 Chiusura del Mixer Controller...") + self._is_running.clear() + if self.socket: + try: + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + except socket.error: + pass + if self._reader_thread and self._reader_thread.is_alive(): + self._reader_thread.join(timeout=2) + print("✅ Controller fermato.") + self._save_cache() def __enter__(self): - """Context manager entry.""" return self def __exit__(self, exc_type, exc_val, exc_tb): - """Context manager exit.""" self.close() + # --- Metodi di gestione della cache e di utilità (invariati) --- + def _sanitize_value(self, value): + 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 + elif value == float('inf') or (isinstance(value, float) and math.isinf(value) and value > 0): return 10.0 + elif isinstance(value, float) and math.isnan(value): return 0.0 + return value + 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(): - try: - with open(CACHE_FILE, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - print(f"⚠️ Errore nel caricamento della cache: {e}") - return {"channels": {}, "mixes": {}, "timestamp": 0} + with self._cache_lock: + if CACHE_FILE.exists(): + try: + with open(CACHE_FILE, 'r', encoding='utf-8') as f: + data = json.load(f) + # Assicura che le chiavi principali esistano + if "channels" not in data: data["channels"] = {} + if "mixes" not in data: data["mixes"] = {} + return data + 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}") + with self._cache_lock: + try: + 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): - return {k: self._prepare_cache_for_json(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [self._prepare_cache_for_json(item) for item in obj] + if isinstance(obj, dict): return {k: self._prepare_cache_for_json(v) for k, v in obj.items()} + elif isinstance(obj, list): return [self._prepare_cache_for_json(item) for item in obj] elif isinstance(obj, float): - # Converti -inf in null per JSON - if obj == float('-inf'): - return None - return obj - else: + if obj == float('-inf'): return None 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 + with self._cache_lock: + 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') + 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) - try: - level_int = int(level_raw) - 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) - try: - 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, - "name": name, - "on": is_on, - "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) - try: - level_int = int(level_raw) - 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, - "name": name, - "on": is_on, - "level_db": level_db - } - - self._save_cache() - + def _parse_name(self, response: str) -> str: - """Estrae il nome tra virgolette dalla prima riga della risposta.""" try: - # Considera solo la prima riga per evitare di includere notifiche successive first_line = response.split('\n')[0] start = first_line.find('"') + 1 end = first_line.rfind('"') - if start > 0 and end > start: - return first_line[start:end] + if start > 0 and end > start: return first_line[start:end] return "Sconosciuto" - except: - return "Errore" + except: return "Errore" def _parse_value(self, response: str) -> str: - """Estrae l'ultimo valore dalla prima riga di una risposta OK.""" - # Considera solo la prima riga per evitare notifiche successive first_line = response.split('\n')[0] parts = first_line.split() - if len(parts) > 0 and parts[0] == "OK": - return parts[-1] + if len(parts) > 0 and parts[0] == "OK": return parts[-1] return "N/A" - + + # --- API Pubblica del Controller (invariata) --- + # ... tutti gli altri metodi da refresh_cache in poi rimangono identici ... 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) - try: - level_int = int(level_raw) - level_db = level_int / 100.0 if level_int > -32768 else float('-inf') - except: - level_db = None - - # Leggi pan + try: level_db = int(level_raw) / 100.0 if int(level_raw) > -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 + try: pan_value = int(pan_raw) + except: pan_value = None + channels_data[str(ch)] = {"channel": ch, "name": name, "on": is_on, "level_db": level_db, "pan": pan_value} + time.sleep(0.01) # Riduci il delay, la connessione persistente è veloce - channels_data[str(ch)] = { - "channel": ch, - "name": name, - "on": is_on, - "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) - try: - level_int = int(level_raw) - level_db = level_int / 100.0 if level_int > -32768 else float('-inf') - except: - level_db = None + try: level_db = int(level_raw) / 100.0 if int(level_raw) > -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.01) - 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() - } + with self._cache_lock: + self._cache = {"channels": channels_data, "mixes": mixes_data, "timestamp": time.time()} self._save_cache() + + msg = f"Cache aggiornata con {len(channels_data)} canali e {len(mixes_data)} mix" + print(f"✅ {msg}") + return {"status": "success", "message": msg, "channels_count": len(channels_data), "mixes_count": len(mixes_data)} - print(f"✅ Cache aggiornata con {len(channels_data)} canali e {len(mixes_data)} mix") - return { - "status": "success", - "message": f"Cache aggiornata con {len(channels_data)} canali e {len(mixes_data)} mix", - "channels_count": len(channels_data), - "mixes_count": len(mixes_data) - } - + # ... TUTTI GLI ALTRI METODI PUBBLICI RESTANO UGUALI ... 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 - """ - if bank.lower() not in ['a', 'b']: - 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"} + if bank.lower() not in ['a', 'b']: 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 + if "OK" in response: + print("Scene richiamata. La cache si aggiornerà tramite NOTIFY. Schedulato un refresh completo in 5s per sicurezza.") + threading.Timer(5.0, self.refresh_cache).start() - return { - "status": "success" if "OK" in response else "error", - "message": f"Scena {bank.upper()}{scene_number} richiamata. Cache invalidata.", - "response": response - } + return {"status": "success" if "OK" in response else "error", "message": f"Scena {bank.upper()}{scene_number} richiamata.", "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) - + if not 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} + internal_value = -32768 if level_db <= -138 else 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 - } + 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}" + if not 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} + command = f"set MIXER:Current/InCh/Fader/On {channel-1} 0 {1 if state else 0}" 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 - } + 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 - """ - if not 1 <= channel <= TF5_INPUT_CHANNELS: - 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"} - + if not 1 <= channel <= TF5_INPUT_CHANNELS: 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 - } + 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) - + if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} + internal_value = -32768 if level_db <= -138 else 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 - } + 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}" + if not 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} + command = f"set MIXER:Current/Mix/Fader/On {mix_number-1} 0 {1 if state else 0}" 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 - } + 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 - """ - results = [] - for ch in channels: - result = self.set_channel_on_off(ch, False) - results.append(result) - + results = [self.set_channel_on_off(ch, False) for ch in channels] 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 - } + 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 - """ - results = [] - for ch in channels: - result = self.set_channel_on_off(ch, True) - results.append(result) - + results = [self.set_channel_on_off(ch, True) for ch in channels] 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 - } + 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 1 <= channel <= TF5_INPUT_CHANNELS: return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"} if not force_refresh and self._is_cache_valid(): - cached_data = self._cache.get("channels", {}).get(str(channel)) + with self._cache_lock: + 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": sanitized_level_db, - "pan": cached_data.get("pan") - } - - # Altrimenti leggi dal mixer + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(cached_data.get("level_db"))) + return {"status": "success", "source": "cache", "channel": cached_data["channel"], "name": cached_data["name"], "on": cached_data["on"], "level_db": sanitized_level_db, "pan": cached_data.get("pan")} + 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: - level_int = int(level_raw) - level_db = level_int / 100.0 if level_int > -32768 else float('-inf') - except: - level_db = None - + try: level_db = int(level_raw) / 100.0 if int(level_raw) > -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": sanitized_level_db, - "pan": pan_value - } + try: pan_value = int(pan_raw) + except: pan_value = None + + with self._cache_lock: + if "channels" not in self._cache: self._cache["channels"] = {} + self._cache["channels"][str(channel)] = {"channel": channel, "name": name, "on": is_on, "level_db": level_db, "pan": pan_value} + self._cache['timestamp'] = time.time() + + return {"status": "success", "source": "mixer", "channel": channel, "name": name, "on": is_on, "level_db": self._sanitize_value(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 1 <= mix_number <= TF5_MIX_BUSSES: return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} if not force_refresh and self._is_cache_valid(): - cached_data = self._cache.get("mixes", {}).get(str(mix_number)) + with self._cache_lock: + 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": sanitized_level_db - } - - # Altrimenti leggi dal mixer + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(cached_data.get("level_db"))) + return {"status": "success", "source": "cache", "mix": cached_data["mix"], "name": cached_data["name"], "on": cached_data["on"], "level_db": sanitized_level_db} + 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: - level_int = int(level_raw) - level_db = level_int / 100.0 if level_int > -32768 else float('-inf') - except: - level_db = None + try: level_db = int(level_raw) / 100.0 if int(level_raw) > -32768 else float('-inf') + except: level_db = None + + with self._cache_lock: + if "mixes" not in self._cache: self._cache["mixes"] = {} + self._cache["mixes"][str(mix_number)] = {"mix": mix_number, "name": name, "on": is_on, "level_db": level_db} + self._cache['timestamp'] = time.time() - sanitized_level_db = self._sanitize_value(level_db) - return { - "status": "success", - "source": "mixer", - "mix": mix_number, - "name": name, - "on": is_on, - "level_db": sanitized_level_db - } + return {"status": "success", "source": "mixer", "mix": mix_number, "name": name, "on": is_on, "level_db": self._sanitize_value(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() - + 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(): + with self._cache_lock: + channels_copy = list(self._cache.get("channels", {}).values()) + for info in channels_copy: 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": sanitized_level_db - }) - - # Ordina per numero canale + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + found_channels.append({"channel": info["channel"], "name": info["name"], "on": info["on"], "level_db": sanitized_level_db}) found_channels.sort(key=lambda x: x["channel"]) - - return { - "status": "success", - "search_term": search_term, - "found_count": len(found_channels), - "channels": found_channels, - "message": f"Trovati {len(found_channels)} canali contenenti '{search_term}'" - } + return {"status": "success", "search_term": search_term, "found_count": len(found_channels), "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() - + 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(): + with self._cache_lock: + mixes_copy = list(self._cache.get("mixes", {}).values()) + for info in mixes_copy: 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": sanitized_level_db - }) - - # Ordina per numero mix + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + found_mixes.append({"mix": info["mix"], "name": info["name"], "on": info["on"], "level_db": sanitized_level_db}) found_mixes.sort(key=lambda x: x["mix"]) - - return { - "status": "success", - "search_term": search_term, - "found_count": len(found_mixes), - "mixes": found_mixes, - "message": f"Trovati {len(found_mixes)} mix contenenti '{search_term}'" - } + return {"status": "success", "search_term": search_term, "found_count": len(found_mixes), "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() - + 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"], - "level_db": sanitized_level_db - }) - - # Ordina per numero canale + with self._cache_lock: + channels_copy = list(self._cache.get("channels", {}).values()) + cache_age = time.time() - self._cache.get("timestamp", 0) + for info in channels_copy: + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + channels.append({"channel": info["channel"], "name": info["name"], "on": info["on"], "level_db": sanitized_level_db}) channels.sort(key=lambda x: x["channel"]) - - cache_age = time.time() - self._cache.get("timestamp", 0) - - return { - "status": "success", - "total_channels": len(channels), - "channels": channels, - "cache_age_seconds": int(cache_age), - "message": f"Riepilogo di {len(channels)} canali (cache: {int(cache_age)}s fa)" - } + return {"status": "success", "total_channels": len(channels), "channels": channels, "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() - + 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"], - "level_db": sanitized_level_db - }) - - # Ordina per numero mix + with self._cache_lock: + mixes_copy = list(self._cache.get("mixes", {}).values()) + cache_age = time.time() - self._cache.get("timestamp", 0) + for info in mixes_copy: + sanitized_level_db = self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + mixes.append({"mix": info["mix"], "name": info["name"], "on": info["on"], "level_db": sanitized_level_db}) mixes.sort(key=lambda x: x["mix"]) - - cache_age = time.time() - self._cache.get("timestamp", 0) - - return { - "status": "success", - "total_mixes": len(mixes), - "mixes": mixes, - "cache_age_seconds": int(cache_age), - "message": f"Riepilogo di {len(mixes)} mix (cache: {int(cache_age)}s fa)" - } + return {"status": "success", "total_mixes": len(mixes), "mixes": mixes, "cache_age_seconds": int(cache_age), "message": f"Riepilogo di {len(mixes)} mix (cache: {int(cache_age)}s fa)"} 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 - """ - if not 1 <= channel <= TF5_INPUT_CHANNELS: - 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) - + if not 1 <= channel <= TF5_INPUT_CHANNELS: 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}"} + internal_value = -32768 if level_db <= -138 else 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", - "response": response - } + return {"status": "success" if "OK" in response else "error", "message": f"Canale {channel} → Mix {mix_number} impostato a {level_db:+.1f} dB", "response": response} 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 - """ - if not 1 <= channel <= TF5_INPUT_CHANNELS: - 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}" + if not 1 <= channel <= TF5_INPUT_CHANNELS: 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}"} + command = f"set MIXER:Current/InCh/ToMix/On {channel-1} {mix_number-1} {1 if state else 0}" 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'}", - "response": response - } + return {"status": "success" if "OK" in response else "error", "message": f"Invio canale {channel} → Mix {mix_number} {'acceso' if state else 'spento'}", "response": response} 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 - """ - if not 1 <= channel <= TF5_INPUT_CHANNELS: - 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 + if not 1 <= channel <= TF5_INPUT_CHANNELS: 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, mix_idx = channel - 1, mix_number - 1 resp_level = self._send_command(f"get MIXER:Current/InCh/ToMix/Level {ch_idx} {mix_idx}") level_raw = self._parse_value(resp_level) - try: - level_int = int(level_raw) - level_db = level_int / 100.0 if level_int > -32768 else float('-inf') - except: - level_db = None - - # Leggi stato ON/OFF + try: level_db = int(level_raw) / 100.0 if int(level_raw) > -32768 else float('-inf') + except: level_db = None 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, - "send_level_db": sanitized_level_db, - "message": f"Canale {channel} → Mix {mix_number}: {sanitized_level_db:+.1f} dB ({'ON' if is_on else 'OFF'})" - } + return {"status": "success", "channel": channel, "mix": mix_number, "on": is_on, "send_level_db": sanitized_level_db, "message": f"Canale {channel} → Mix {mix_number}: {sanitized_level_db:+.1f} dB ({'ON' if is_on else 'OFF'})"} def get_full_mix_details(self, mix_number: int) -> dict: - """ - Raccoglie informazioni complete su un mix, inclusi tutti i canali inviati ad esso. - Questa funzione è fondamentale per l'analisi del mix. - - Args: - mix_number: Numero del mix (1-20) - - Returns: - Un dizionario con i dettagli del mix e una lista dei canali inviati. - """ - if not 1 <= mix_number <= TF5_MIX_BUSSES: - return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"} - - # Aggiorna la cache se non è valida per avere i nomi dei canali - if not self._is_cache_valid(): - self.refresh_cache() - - # Ottieni le info principali 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}"} + if not self._is_cache_valid(): self.refresh_cache() mix_info = self.get_mix_info(mix_number) - if mix_info["status"] != "success": - return mix_info - + if mix_info["status"] != "success": return mix_info sends = [] - # Itera su tutti i canali di input per vedere cosa mandano a questo mix for channel in range(1, TF5_INPUT_CHANNELS + 1): send_info = self.get_channel_to_mix_info(channel, mix_number) - - # Aggiungi solo i canali che sono effettivamente inviati al mix if send_info["status"] == "success" and send_info["send_level_db"] > -120.0: - # Recupera le info principali del canale dalla cache per efficienza - channel_cache = self._cache.get("channels", {}).get(str(channel), {}) - - sends.append({ - "channel": channel, - "channel_name": channel_cache.get("name", "Sconosciuto"), - "channel_is_on": channel_cache.get("on", False), - "send_level_db": send_info["send_level_db"], - "send_is_on": send_info["on"] - }) - - # Ordina i canali dal più forte al più debole per facilitare l'analisi + with self._cache_lock: + channel_cache = self._cache.get("channels", {}).get(str(channel), {}) + sends.append({"channel": channel, "channel_name": channel_cache.get("name", "Sconosciuto"), "channel_is_on": channel_cache.get("on", False), "send_level_db": send_info["send_level_db"], "send_is_on": send_info["on"]}) sends.sort(key=lambda x: x["send_level_db"], reverse=True) - - return { - "status": "success", - "mix_details": mix_info, - "active_sends": sends, - "message": f"Dettagli completi per il Mix {mix_number} recuperati." - } \ No newline at end of file + return {"status": "success", "mix_details": mix_info, "active_sends": sends, "message": f"Dettagli completi per il Mix {mix_number} recuperati."} \ No newline at end of file diff --git a/profiles/1552084582.json b/profiles/1552084582.json new file mode 100644 index 0000000..e2f0484 --- /dev/null +++ b/profiles/1552084582.json @@ -0,0 +1,17 @@ +{ + "user_id": 1552084582, + "username": "laissaantuness", + "role": "musicista", + "display_name": "Lai", + "channel_labels": [ + "Chitarra" + ], + "mix_labels": [ + "Aux 7" + ], + "setup_completed": true, + "created_at": 1762199748.9965794, + "created_at_str": "2025-11-03 19:55:48", + "last_updated": 1762199837.8484952, + "last_updated_str": "2025-11-03 19:57:17" +} \ No newline at end of file diff --git a/profiles/309700430.json b/profiles/309700430.json new file mode 100644 index 0000000..d1e657e --- /dev/null +++ b/profiles/309700430.json @@ -0,0 +1,13 @@ +{ + "user_id": 309700430, + "username": "Cicciomt7", + "role": "mixerista", + "display_name": "Frank", + "channel_labels": [], + "mix_labels": [], + "setup_completed": true, + "created_at": 1762198859.8032317, + "created_at_str": "2025-11-03 19:40:59", + "last_updated": 1762198880.1237595, + "last_updated_str": "2025-11-03 19:41:20" +} \ No newline at end of file diff --git a/profiles/392578198.json b/profiles/392578198.json new file mode 100644 index 0000000..604d436 --- /dev/null +++ b/profiles/392578198.json @@ -0,0 +1,17 @@ +{ + "user_id": 392578198, + "username": "Unknown", + "role": "cantante", + "display_name": "Rox", + "channel_labels": [ + "Vox1" + ], + "mix_labels": [ + "Aux2" + ], + "setup_completed": true, + "created_at": 1762199748.3117115, + "created_at_str": "2025-11-03 19:55:48", + "last_updated": 1762199831.8709695, + "last_updated_str": "2025-11-03 19:57:11" +} \ No newline at end of file diff --git a/profiles/436176546.json b/profiles/436176546.json new file mode 100644 index 0000000..6e2d3e0 --- /dev/null +++ b/profiles/436176546.json @@ -0,0 +1,17 @@ +{ + "user_id": 436176546, + "username": "Gloriushhh", + "role": "cantante", + "display_name": "Gloriush", + "channel_labels": [ + "VOX 2" + ], + "mix_labels": [ + "AUX 3" + ], + "setup_completed": true, + "created_at": 1762199750.435028, + "created_at_str": "2025-11-03 19:55:50", + "last_updated": 1762199825.8800879, + "last_updated_str": "2025-11-03 19:57:05" +} \ No newline at end of file diff --git a/profiles/4762864.json b/profiles/4762864.json index 63bd6dd..5ade06c 100644 --- a/profiles/4762864.json +++ b/profiles/4762864.json @@ -1,17 +1,21 @@ { "user_id": 4762864, "username": "unnikked", - "role": "cantante", + "role": "mixerista", "display_name": "Nicola", "channel_labels": [ - "Vox1" + "I canali della batteria (kick", + "drum", + "pan", + "tom", + "etc)" ], "mix_labels": [ - "Aux3" + "Aux batteria" ], "setup_completed": true, "created_at": 1762198047.365003, "created_at_str": "2025-11-03 20:27:27", - "last_updated": 1762198068.4479709, - "last_updated_str": "2025-11-03 20:27:48" + "last_updated": 1762799485.4542227, + "last_updated_str": "2025-11-10 19:31:25" } \ No newline at end of file diff --git a/profiles/540754896.json b/profiles/540754896.json new file mode 100644 index 0000000..207ea04 --- /dev/null +++ b/profiles/540754896.json @@ -0,0 +1,17 @@ +{ + "user_id": 540754896, + "username": "merymoon001", + "role": "musicista", + "display_name": "Moon", + "channel_labels": [ + "Tastiera" + ], + "mix_labels": [ + "aux 8" + ], + "setup_completed": true, + "created_at": 1762199744.617781, + "created_at_str": "2025-11-03 19:55:44", + "last_updated": 1762199821.1137702, + "last_updated_str": "2025-11-03 19:57:01" +} \ No newline at end of file