diff --git a/.tf5_mixer_cache/channels_cache.json b/.tf5_mixer_cache/channels_cache.json index 3697599..d452a92 100644 --- a/.tf5_mixer_cache/channels_cache.json +++ b/.tf5_mixer_cache/channels_cache.json @@ -11,7 +11,7 @@ "channel": 2, "name": "Gelato", "on": false, - "level_db": null, + "level_db": -8.4, "pan": 0 }, "3": { @@ -31,15 +31,15 @@ "5": { "channel": 5, "name": "Vox1", - "on": false, - "level_db": null, + "on": true, + "level_db": 0.8, "pan": 0 }, "6": { "channel": 6, "name": "Vox2", - "on": false, - "level_db": null, + "on": true, + "level_db": 2.75, "pan": 0 }, "7": { @@ -59,50 +59,50 @@ "9": { "channel": 9, "name": "Kick", - "on": false, - "level_db": null, + "on": true, + "level_db": -1.25, "pan": 0 }, "10": { "channel": 10, "name": "Snare", "on": false, - "level_db": null, + "level_db": -2.35, "pan": 0 }, "11": { "channel": 11, "name": "Tom 1", "on": false, - "level_db": null, + "level_db": -3.55, "pan": 0 }, "12": { "channel": 12, "name": "Tom 2", "on": false, - "level_db": null, + "level_db": -5.9, "pan": 0 }, "13": { "channel": 13, "name": "Tom3", "on": false, - "level_db": null, + "level_db": -8.65, "pan": 0 }, "14": { "channel": 14, "name": "Pan SX", "on": false, - "level_db": null, + "level_db": 0.85, "pan": 0 }, "15": { "channel": 15, "name": "Pan dx", "on": false, - "level_db": null, + "level_db": 0.45, "pan": 0 }, "16": { @@ -136,8 +136,8 @@ "20": { "channel": 20, "name": "Tast", - "on": false, - "level_db": null, + "on": true, + "level_db": 0.15, "pan": 0 }, "21": { @@ -157,15 +157,15 @@ "23": { "channel": 23, "name": " Vox3", - "on": false, - "level_db": null, + "on": true, + "level_db": 4.6, "pan": 0 }, "24": { "channel": 24, "name": "Chit cnt", - "on": false, - "level_db": null, + "on": true, + "level_db": -1.95, "pan": 0 }, "25": { @@ -185,7 +185,7 @@ "27": { "channel": 27, "name": "Vox 4", - "on": false, + "on": true, "level_db": null, "pan": 0 }, @@ -228,14 +228,14 @@ "channel": 33, "name": "PC", "on": true, - "level_db": -17.5, + "level_db": -92.0, "pan": 0 }, "34": { "channel": 34, "name": "PC", "on": true, - "level_db": -17.5, + "level_db": -92.0, "pan": 63 }, "35": { @@ -328,7 +328,7 @@ "mix": 8, "name": "Aux 8", "on": true, - "level_db": -9.0 + "level_db": -2.15 }, "9": { "mix": 9, @@ -403,12 +403,71 @@ "level_db": 6.0 } }, - "timestamp": 1770577942.7806144, + "timestamp": 1776106373.6489363, "steinch": {}, - "fxrtn": {}, - "dcas": {}, - "matrices": {}, - "stereo": {}, - "mono": {}, + "fxrtn": { + "1": { + "channel": 1, + "level_db": -18.0 + }, + "2": { + "channel": 2, + "level_db": -18.0 + }, + "3": { + "channel": 3, + "on": true, + "level_db": -20.0 + }, + "4": { + "channel": 4, + "on": true, + "level_db": -20.0 + } + }, + "dcas": { + "3": { + "dca": 3, + "level_db": 0.25 + }, + "4": { + "dca": 4, + "level_db": 1.2 + }, + "2": { + "dca": 2, + "level_db": 1.3 + } + }, + "matrices": { + "3": { + "matrix": 3, + "level_db": -30.5 + }, + "1": { + "matrix": 1, + "level_db": -17.3 + }, + "2": { + "matrix": 2, + "level_db": -16.8 + } + }, + "stereo": { + "1": { + "bus": 1, + "name": "Stereo", + "on": true, + "level_db": -2.15 + }, + "2": { + "bus": 2, + "on": true, + "level_db": -2.15 + } + }, + "mono": { + "on": true + }, "mute_masters": {} } \ No newline at end of file diff --git a/__pycache__/config.cpython-312.pyc b/__pycache__/config.cpython-312.pyc index aa1bf7b..5c5bc45 100644 Binary files a/__pycache__/config.cpython-312.pyc and b/__pycache__/config.cpython-312.pyc differ diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..a47d332 Binary files /dev/null and b/__pycache__/main.cpython-312.pyc differ diff --git a/__pycache__/mixer_controller.cpython-312.pyc b/__pycache__/mixer_controller.cpython-312.pyc index 02c0736..4853ee1 100644 Binary files a/__pycache__/mixer_controller.cpython-312.pyc and b/__pycache__/mixer_controller.cpython-312.pyc differ diff --git a/config.py b/config.py index 82fb038..3a7523b 100644 --- a/config.py +++ b/config.py @@ -11,6 +11,7 @@ import time DEFAULT_PORT = 49280 TF5_INPUT_CHANNELS = 40 TF5_MIX_BUSSES = 20 +TF5_STEREO_BUSSES = 1 # Configurazione cache CACHE_DIR = Path(".tf5_mixer_cache") diff --git a/conversations/4762864.json b/conversations/4762864.json index 921c0b0..953ea7b 100644 --- a/conversations/4762864.json +++ b/conversations/4762864.json @@ -1,135 +1,135 @@ { "user_id": 4762864, "username": "unnikked", - "last_updated": 1770578542.7348943, - "last_updated_str": "2026-02-08 19:22:22", + "last_updated": 1776010014.91853, + "last_updated_str": "2026-04-12 16:06:54", "message_count": 21, "history": [ + { + "role": "assistant", + "content": "Certo Nicola, scusa! Ho capito, vuoi i canali aperti e i loro volumi attuali.\n\nEcco l'istantanea dei canali attualmente accesi e i loro livelli:\n\n✅ **Canali ON e Volumi:**\n\n* **Talkback** (Canale 3): **-120.0 dB** (molto basso, quasi muto)\n* **Vox2** (Canale 6): **-1.9 dB**\n* **Kick** (Canale 9): **0.0 dB**\n* **Snare** (Canale 10): **0.0 dB**\n* **Tom 1** (Canale 11): **0.0 dB**\n* **Tom 2** (Canale 12): **0.0 dB**\n* **Tom3** (Canale 13): **0.0 dB**\n* **Pan SX** (Canale 14): **-7.2 dB**\n* **Pan dx** (Canale 15): **-7.2 dB**\n* **Tast** (Canale 20): **+9.0 dB**\n* **Vox3** (Canale 23): **+2.4 dB**\n* **Chit cnt** (Canale 24): **-8.2 dB**\n* **Vox 4** (Canale 27): **-120.0 dB** (molto basso, quasi muto)\n* **PC** (Canale 33): **-120.0 dB** (molto basso, quasi muto)\n* **PC** (Canale 34): **-120.0 dB** (molto basso, quasi muto)\n* **ch35** (Canale 35): **-120.0 dB** (molto basso, quasi muto)\n* **ch36** (Canale 36): **-120.0 dB** (molto basso, quasi muto)\n* **ch37** (Canale 37): **-120.0 dB** (molto basso, quasi muto)\n* **ch38** (Canale 38): **-120.0 dB** (molto basso, quasi muto)\n* **ch39** (Canale 39): **-120.0 dB** (molto basso, quasi muto)\n* **ch40** (Canale 40): **-120.0 dB** (molto basso, quasi muto)\n\nNota che alcuni canali sono \"ON\" ma con un volume a -120 dB, il che significa che sono di fatto silenziati.\n\nFammi sapere se ti serve altro! 🎤🎸🥁🔊", + "timestamp": 1775407332.485904, + "timestamp_str": "2026-04-05 16:42:12" + }, { "role": "user", - "content": "Controlla ora", - "timestamp": 1769455908.2156205, - "timestamp_str": "2026-01-26 19:31:48" + "content": "Alza un po' i pan della batteria", + "timestamp": 1775407389.2330492, + "timestamp_str": "2026-04-05 16:43:09" }, { "role": "assistant", - "content": "Allora, Nicola, ho controllato di nuovo il mixer dopo il refresh! Ecco cosa vedo:\n\n**Canali:**\nCi sono un bel po' di canali attivi e nominati:\n* **Gelato (Canale 2):** Acceso, volume a -11.7 dB\n* **Vox1 (Canale 5):** Acceso, volume a 0 dB\n* **Vox2 (Canale 6):** Acceso, volume a 6.3 dB\n* **Basso (Canale 7):** Acceso, volume a -2.15 dB\n* **Kick (Canale 9):** Acceso, volume a -0.35 dB\n* **Snare (Canale 10):** Acceso, volume a -1.6 dB\n* **Tom 1 (Canale 11):** Acceso, volume a -2.8 dB\n* **Tom 2 (Canale 12):** Acceso, volume a -0.8 dB\n* **Tom3 (Canale 13):** Acceso, volume a -3.05 dB\n* **Pan SX (Canale 14):** Acceso, volume a -3.85 dB\n* **Pan dx (Canale 15):** Acceso, volume a -4.3 dB\n* **Archetto (Canale 18):** Acceso, volume a 4.6 dB\n* **Vox 5 (Canale 19):** Acceso, volume a -4.65 dB\n* **Tast (Canale 20):** Acceso, volume a -1.6 dB\n* **Vox3 (Canale 23):** Acceso, volume a 3.05 dB\n* **Chit cnt (Canale 24):** Acceso, volume a -2.35 dB\n* **Chit dx (Canale 25):** Acceso, volume a -0.85 dB\n* **PC (Canale 33 e 34):** Accesi, volume a -43.2 dB\n\n**Canali che sono accesi ma con volume bassissimo (-120 dB), quindi praticamente muti:**\n* **Vox 4 (Canale 27)**\n* **ch35 (Canale 35)**\n* **ch36 (Canale 36)**\n* **ch37 (Canale 37)**\n* **ch38 (Canale 38)**\n* **ch39 (Canale 39)**\n* **ch40 (Canale 40)**\n\n**Canali spenti o sconosciuti:**\n* **Canale 1:** Sconosciuto, spento, volume a 0.65 dB\n* **Talkback (Canale 3):** Spento, volume a -120 dB\n* **Canale 4:** Sconosciuto, spento, volume a -120 dB\n* **Canale 8:** Sconosciuto, spento, volume a -120 dB\n* **Canale 16:** Sconosciuto, spento, volume a -120 dB\n* **Canale 17:** Sconosciuto, spento, volume a -120 dB\n* **Canale 21:** Sconosciuto, spento, volume a -120 dB\n* **Canale 22:** Sconosciuto, spento, volume a -120 dB\n* **Canale 26:** Sconosciuto, spento, volume a -120 dB\n* **Canale 28:** Sconosciuto, spento, volume a -120 dB\n* **Pad (Canale 29):** Spento, volume a -120 dB\n* **Canali dal 30 al 32:** Sconosciuti, spenti, volume a -120 dB\n\n**Mix:**\nAnche i mix sono belli attivi:\n* **Aux 1 (Mix 1):** Acceso, volume a -11.1 dB\n* **Aux 2 (Mix 2):** Acceso, volume a -0.05 dB\n* **Aux 3 (Mix 3):** Acceso, volume a 0.35 dB\n* **Aux 4 (Mix 4):** Acceso, volume a 0 dB\n* **batteria (Mix 5):** Acceso, volume a 1.35 dB\n* **Aux 6 (Mix 6):** Acceso, volume a 10 dB\n* **Aux 7 (Mix 7):** Acceso, volume a -9.75 dB\n* **Aux 8 (Mix 8):** Acceso, volume a -4.4 dB\n* **Aux 9/10 (Mix 9 e 10):** Accesi, volume a -8.1 dB\n* **Aux Pulp (Mix 11 e 12):** Accesi, volume a -10.3 dB\n* **Aux15/16 (Mix 15 e 16):** Accesi, volume a 0.1 dB\n* **Diretta (Mix 17 e 18):** Accesi, volume a -2 dB\n* **Traduzio (Mix 19 e 20):** Accesi, volume a 6 dB\n\n**Mix spenti:**\n* **Aux13/14 (Mix 13 e 14):** Spenti, volume a -1.6 dB\n\nLa cache è stata aggiornata 5 secondi fa. Sembra che ci sia un buon movimento sul mixer! Fammi sapere se vuoi che faccia qualche modifica. 🎛️🎤", - "timestamp": 1769455917.246521, - "timestamp_str": "2026-01-26 19:31:57" + "content": "✅ Capito Nicola! Ho visto che tutti i canali della batteria erano centrati. Per \"alzare un po' i pan\" e dare più ampiezza alla batteria, li ho distribuiti nel campo stereo in modo più classico.\n\nEcco cosa ho fatto:\n\n* **Kick (Canale 9)**: L'ho lasciato al centro (pan 0). 🥁\n* **Snare (Canale 10)**: L'ho lasciato al centro (pan 0). 🥁\n* **Tom 1 (Canale 11)**: Spostato a **-20** (un po' a sinistra). 🥁\n* **Tom 2 (Canale 12)**: Spostato a **+20** (un po' a destra). 🥁\n* **Tom3 (Canale 13)**: Spostato a **-10** (leggermente a sinistra). 🥁\n* **Pan SX (Canale 14)**: Spostato a **-63** (tutto a sinistra). 🔊\n* **Pan dx (Canale 15)**: Spostato a **+63** (tutto a destra). 🔊\n\nOra dovresti sentire la batteria con una bella apertura stereo! Fammi sapere come suona!", + "timestamp": 1775407402.6096156, + "timestamp_str": "2026-04-05 16:43:22" }, { "role": "user", - "content": "ciao", - "timestamp": 1769766889.7665708, - "timestamp_str": "2026-01-30 09:54:49" + "content": "Mi riferivo a Pan dx e Pan sx i microfoni panoramici", + "timestamp": 1775407438.4079397, + "timestamp_str": "2026-04-05 16:43:58" }, { "role": "assistant", - "content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤", - "timestamp": 1769766891.7504349, - "timestamp_str": "2026-01-30 09:54:51" + "content": "Ah, scusa Nicola, ho capito male! 🤦‍♂️ Pensavo ti riferissi al panning dei singoli elementi della batteria.\n\nCerto, per \"Pan SX\" (Canale 14) e \"Pan dx\" (Canale 15), i microfoni panoramici, li ho già messi tutto a sinistra e tutto a destra (-63 e +63).\n\nPer \"alzare un po' i pan\" intendi aumentare il loro volume generale, giusto?\n\nSe sì, li alzo di circa 3 dB. Fammi sapere!", + "timestamp": 1775407442.692902, + "timestamp_str": "2026-04-05 16:44:02" }, { "role": "user", - "content": "è acceso?", - "timestamp": 1769766900.066535, - "timestamp_str": "2026-01-30 09:55:00" + "content": "Sì", + "timestamp": 1775407452.6158352, + "timestamp_str": "2026-04-05 16:44:12" }, { "role": "assistant", - "content": "Cosa intendi? Un canale, un mix, o qualcos'altro? 🧐", - "timestamp": 1769766901.4737165, - "timestamp_str": "2026-01-30 09:55:01" + "content": "✅ Fatto, Nicola! Ho alzato il volume dei microfoni panoramici:\n\n* **Pan SX (Canale 14)**: da -7.2 dB a **-4.2 dB** 🔊\n* **Pan dx (Canale 15)**: da -7.2 dB a **-4.2 dB** 🔊\n\nOra dovrebbero essere un po' più presenti nel mix! Fammi sapere come suona!", + "timestamp": 1775407454.4361358, + "timestamp_str": "2026-04-05 16:44:14" }, { "role": "user", - "content": "proprio il mixer, lo riesci a raggiungere?", - "timestamp": 1769766912.3007145, - "timestamp_str": "2026-01-30 09:55:12" + "content": "Rifammi listantanea come prima includi anche il pan", + "timestamp": 1775407504.1431096, + "timestamp_str": "2026-04-05 16:45:04" }, { "role": "assistant", - "content": "✅ Certo, Nicola! Ho appena fatto un refresh e ho ricaricato tutte le informazioni dal mixer. Sembra che sia bello sveglio e raggiungibile! 🎛️✨", - "timestamp": 1769768014.9555686, - "timestamp_str": "2026-01-30 10:13:34" + "content": "Certo Nicola, ecco l'istantanea completa dei canali attualmente accesi, con i loro volumi e il bilanciamento (pan):\n\n✅ **Canali ON, Volumi e Pan:**\n\n* **Talkback** (Canale 3): **ON**, Volume: **-60.0 dB**, Pan: **0** (Centro)\n* **Vox2** (Canale 6): **ON**, Volume: **+2.55 dB**, Pan: **0** (Centro)\n* **Kick** (Canale 9): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **Snare** (Canale 10): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **Tom 1** (Canale 11): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **Tom 2** (Canale 12): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **Tom3** (Canale 13): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **Pan SX** (Canale 14): **ON**, Volume: **-9.45 dB**, Pan: **0** (Centro)\n* **Pan dx** (Canale 15): **ON**, Volume: **+4.15 dB**, Pan: **0** (Centro)\n* **Tast** (Canale 20): **ON**, Volume: **+2.45 dB**, Pan: **0** (Centro)\n* **Vox3** (Canale 23): **ON**, Volume: **+1.55 dB**, Pan: **0** (Centro)\n* **Chit cnt** (Canale 24): **ON**, Volume: **+1.2 dB**, Pan: **0** (Centro)\n* **Vox 4** (Canale 27): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **PC** (Canale 33): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **PC** (Canale 34): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **+63** (Tutto a destra)\n* **ch35** (Canale 35): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **ch36** (Canale 36): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **ch37** (Canale 37): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **ch38** (Canale 38): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **ch39** (Canale 39): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n* **ch40** (Canale 40): **ON**, Volume: **-120.0 dB** (quasi muto), Pan: **0** (Centro)\n\nNota che molti canali sono \"ON\" ma con un volume a -120 dB, il che significa che sono di fatto silenziati.\nInoltre, sembra che i pan dei canali della batteria e dei microfoni panoramici siano tornati a 0, tranne per il canale 34. Se vuoi che li risistemi come prima, fammelo sapere!", + "timestamp": 1775407519.41239, + "timestamp_str": "2026-04-05 16:45:19" }, { "role": "user", - "content": "System check", - "timestamp": 1770577025.9354753, - "timestamp_str": "2026-02-08 18:57:05" - }, - { - "role": "user", - "content": "System check", - "timestamp": 1770577350.9690044, - "timestamp_str": "2026-02-08 19:02:30" - }, - { - "role": "user", - "content": "System check", - "timestamp": 1770577683.8676918, - "timestamp_str": "2026-02-08 19:08:03" - }, - { - "role": "user", - "content": "System check", - "timestamp": 1770577775.3157346, - "timestamp_str": "2026-02-08 19:09:35" - }, - { - "role": "user", - "content": "System check", - "timestamp": 1770577899.7554913, - "timestamp_str": "2026-02-08 20:11:39" - }, - { - "role": "user", - "content": "System check", - "timestamp": 1770577940.3210933, - "timestamp_str": "2026-02-08 20:12:20" + "content": "A tabella ?", + "timestamp": 1775407536.7444155, + "timestamp_str": "2026-04-05 16:45:36" }, { "role": "assistant", - "content": "✅ Fatto, Nicola! Ho appena fatto un controllo completo del sistema e ricaricato tutte le informazioni dal mixer. È bello sveglio e raggiungibile! 🎛️✨", - "timestamp": 1770577943.5131805, - "timestamp_str": "2026-02-08 20:12:23" + "content": "Certo Nicola, ecco l'istantanea completa dei canali attualmente accesi, con i loro volumi e il bilanciamento (pan), in formato tabella:\n\n✅ **Canali ON, Volumi e Pan:**\n\n| Canale | Nome | Volume (dB) | Pan |\n|--------|------------|-------------|-----------|\n| 3 | Talkback | -60.0 | Centro |\n| 6 | Vox2 | +2.55 | Centro |\n| 9 | Kick | -120.0 | Centro |\n| 10 | Snare | -120.0 | Centro |\n| 11 | Tom 1 | -120.0 | Centro |\n| 12 | Tom 2 | -120.0 | Centro |\n| 13 | Tom3 | -120.0 | Centro |\n| 14 | Pan SX | -9.45 | Centro |\n| 15 | Pan dx | +4.15 | Centro |\n| 20 | Tast | +2.45 | Centro |\n| 23 | Vox3 | +1.55 | Centro |\n| 24 | Chit cnt | +1.2 | Centro |\n| 27 | Vox 4 | -120.0 | Centro |\n| 33 | PC | -120.0 | Centro |\n| 34 | PC | -120.0 | Destra (+63)|\n| 35 | ch35 | -120.0 | Centro |\n| 36 | ch36 | -120.0 | Centro |\n| 37 | ch37 | -120.0 | Centro |\n| 38 | ch38 | -120.0 | Centro |\n| 39 | ch39 | -120.0 | Centro |\n| 40 | ch40 | -120.0 | Centro |\n\n**Nota:** Molti canali sono \"ON\" ma con un volume a -120 dB, il che significa che sono di fatto silenziati. Inoltre, sembra che i pan dei canali della batteria e dei microfoni panoramici siano tornati a 0, tranne per il canale 34. Se vuoi che li risistemi come prima, fammelo sapere!", + "timestamp": 1775407544.6412542, + "timestamp_str": "2026-04-05 16:45:44" }, { "role": "user", - "content": "Ciao", - "timestamp": 1770577995.903267, - "timestamp_str": "2026-02-08 19:13:15" + "content": "Confrontalo con quello che c'è ora", + "timestamp": 1776009839.2977962, + "timestamp_str": "2026-04-12 16:03:59" }, { "role": "assistant", - "content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤", - "timestamp": 1770577997.697525, - "timestamp_str": "2026-02-08 19:13:17" + "content": "Certo Nicola, capisco! Vuoi confrontare l'istantanea che ti ho dato prima con la situazione attuale del mixer, inclusi i valori di pan.\n\nPer fare un confronto preciso, devo recuperare le informazioni attuali di ogni canale che era presente nella tabella precedente. Questo include lo stato (acceso/spento), il volume e il bilanciamento (pan).\n\nDammi un momento mentre recupero tutti i dati aggiornati dal mixer.", + "timestamp": 1776009859.2585912, + "timestamp_str": "2026-04-12 16:04:19" }, { "role": "user", - "content": "Ciao pupo", - "timestamp": 1770578526.691519, - "timestamp_str": "2026-02-08 19:22:06" + "content": "E prenditi sto tempo, mi raccomando mix 17 e mix 18", + "timestamp": 1776009916.1576858, + "timestamp_str": "2026-04-12 16:05:16" }, { "role": "assistant", - "content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤", - "timestamp": 1770578528.1236699, - "timestamp_str": "2026-02-08 19:22:08" + "content": "Ok Nicola, nessun problema! Mi prendo tutto il tempo che serve per recuperare i dati aggiornati e fare un confronto preciso, specialmente per i Mix 17 e 18.\n\nRecupero subito tutte le informazioni attuali dei canali e dei mix. Ti darò un'analisi dettagliata appena ho tutto. Resta in attesa! 🎧", + "timestamp": 1776009919.148912, + "timestamp_str": "2026-04-12 16:05:19" }, { "role": "user", - "content": "Check canali", - "timestamp": 1770578537.6022112, - "timestamp_str": "2026-02-08 19:22:17" + "content": "Quanto tempo ti serve? Ci sei da una vita", + "timestamp": 1776009934.70995, + "timestamp_str": "2026-04-12 16:05:34" }, { "role": "assistant", - "content": "Allora, Nicola, ho controllato tutti i canali! Ecco la situazione attuale:\n\n**Canali Attivi (o con volume udibile):**\n* **PC (Canale 33 e 34):** Accesi, volume a -17.5 dB\n\n**Canali che sono accesi ma con volume bassissimo (-120 dB), quindi praticamente muti:**\n* **Gelato (Canale 2)**\n* **Vox1 (Canale 5)**\n* **Vox2 (Canale 6)**\n* **Basso (Canale 7)**\n* **Kick (Canale 9)**\n* **Snare (Canale 10)**\n* **Tom 1 (Canale 11)**\n* **Tom 2 (Canale 12)**\n* **Tom3 (Canale 13)**\n* **Pan SX (Canale 14)**\n* **Pan dx (Canale 15)**\n* **Vox 5 (Canale 19)**\n* **Tast (Canale 20)**\n* **Vox3 (Canale 23)**\n* **Chit cnt (Canale 24)**\n* **Chit dx (Canale 25)**\n* **Vox 4 (Canale 27)**\n* **ch35 (Canale 35)**\n* **ch36 (Canale 36)**\n* **ch37 (Canale 37)**\n* **ch38 (Canale 38)**\n* **ch39 (Canale 39)**\n* **ch40 (Canale 40)**\n\n**Canali spenti o sconosciuti:**\n* **Canale 1:** Sconosciuto, spento, volume a 0.65 dB\n* **Talkback (Canale 3):** Spento, volume a -60 dB\n* **Canale 4:** Sconosciuto, spento, volume a -120 dB\n* **Canale 8:** Sconosciuto, spento, volume a -120 dB\n* **Canale 16:** Sconosciuto, spento, volume a -120 dB\n* **Canale 17:** Sconosciuto, spento, volume a -120 dB\n* **Archetto (Canale 18):** Spento, volume a -76 dB\n* **Canale 21:** Sconosciuto, spento, volume a -120 dB\n* **Canale 22:** Sconosciuto, spento, volume a -120 dB\n* **Canale 26:** Sconosciuto, spento, volume a -120 dB\n* **Canale 28:** Sconosciuto, spento, volume a -120 dB\n* **Pad (Canale 29):** Spento, volume a -120 dB\n* **Canali dal 30 al 32:** Sconosciuti, spenti, volume a -120 dB\n\nLa cache è stata aggiornata circa 10 minuti fa. Sembra che molti canali siano spenti o con volumi molto bassi. Fammi sapere se vuoi che accenda o alzi qualcosa! 🎛️🎤", - "timestamp": 1770578542.7348733, - "timestamp_str": "2026-02-08 19:22:22" + "content": "✅ Fatto Nicola! Ho recuperato tutti i dati attuali dal mixer.\n\nEcco il confronto tra l'istantanea precedente e lo stato attuale dei canali, inclusi volume e pan. Ho evidenziato le differenze:\n\n### 📊 Confronto Canali: Prima vs. Ora\n\n| Canale | Nome | Stato Precedente (ON) | Stato Attuale | Note \nNicola, ho notato una cosa importante: molti canali che prima risultavano \"ON\" ora sono \"OFF\" sul mixer. Questo significa che, anche se il volume e il pan sono impostati, il segnale non passa.\n\nEcco il confronto dettagliato:\n\n| Canale | Nome | Stato Precedente (ON) | Stato Attuale | Note |\n|--------|------------|----------------------------|----------------------------|------------------------------------------|\n| 3 | Talkback | ON, -60.0 dB, Centro | **OFF**, -60.0 dB, Centro | ❌ **Spento!** |\n| 6 | Vox2 | ON, +2.55 dB, Centro | **OFF**, +4.05 dB, Centro | ❌ **Spento!**, Volume: **+1.5 dB** (differenza) |\n| 9 | Kick | ON, -120.0 dB, Centro | **OFF**, -4.95 dB, Centro | ❌ **Spento!**, Volume: **+115.05 dB** (differenza)|\n| 10 | Snare | ON, -120.0 dB, Centro | **OFF**, -5.4 dB, Centro | ❌ **Spento!**, Volume: **+114.6 dB** (differenza) |\n| 11 | Tom 1 | ON, -120.0 dB, Centro | **OFF**, -4.85 dB, Centro | ❌ **Spento!**, Volume: **+115.15 dB** (differenza)|\n| 12 | Tom 2 | ON, -120.0 dB, Centro | ON, -4.9 dB, Centro | Volume: **+115.1 dB** (differenza) |\n| 13 | Tom3 | ON, -120.0 dB, Centro | **OFF**, -4.9 dB, Centro | ❌ **Spento!**, Volume: **+115.1 dB** (differenza) |\n| 14 | Pan SX | ON, -9.45 dB, Centro | **OFF**, -120.0 dB, Centro | ❌ **Spento!**, Volume: **-110.55 dB** (differenza)|\n| 15 | Pan dx | ON, +4.15 dB, Centro | **OFF**, +3.15 dB, Centro | ❌ **Spento!**, Volume: **-1.0 dB** (differenza) |\n| 20 | Tast | ON, +2.45 dB, Centro | **OFF**, +1.45 dB, Centro | ❌ **Spento!**, Volume: **-1.0 dB** (differenza) |\n| 23 | Vox3 | ON, +1.55 dB, Centro | **OFF**, +0.65 dB, Centro | ❌ **Spento!**, Volume: **-0.9 dB** (differenza) |\n| 24 | Chit cnt | ON, +1.2 dB, Centro | ON, +3.0 dB, Centro | Volume: **+1.8 dB** (differenza) |\n| 27 | Vox 4 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 33 | PC | ON, -120.0 dB, Centro | ON, -12.7 dB, Centro | Volume: **+107.3 dB** (differenza) |\n| 34 | PC | ON, -120.0 dB, Destra (+63)| ON, -12.7 dB, Destra (+63) | Volume: **+107.3 dB** (differenza) |\n| 35 | ch35 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 36 | ch36 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 37 | ch37 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 38 | ch38 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 39 | ch39 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n| 40 | ch40 | ON, -120.0 dB, Centro | ON, -120.0 dB, Centro | Nessun cambiamento |\n\n**Riassunto delle modifiche principali:**\n\n* **Molti canali sono stati spenti!** ❌ Canali come Talkback, Vox2, Kick, Snare, Tom 1, Tom3, Pan SX, Pan dx, Tast, Vox3 sono ora OFF, anche se alcuni hanno volumi impostati. Questo significa che non sentirai nulla da questi canali.\n* **Volumi cambiati:** Alcuni canali hanno avuto variazioni di volume significative, sia in aumento che in diminuzione.\n* **Pan:** I valori di pan sono rimasti invariati per tutti i canali rispetto all'ultima istantanea che ti ho dato.\n\nVuoi che riaccenda i canali che sono stati spenti?\n\nHai anche menzionato i **Mix 17 e 18**. Vuoi che ti faccia un'analisi dettagliata di come sono configurati questi mix, magari per capire cosa ci sta andando dentro e a che livello?", + "timestamp": 1776009978.796393, + "timestamp_str": "2026-04-12 16:06:18" + }, + { + "role": "user", + "content": "Ci stai girando intorno?", + "timestamp": 1776009979.6229928, + "timestamp_str": "2026-04-12 16:06:19" + }, + { + "role": "assistant", + "content": "No Nicola, non ci sto girando intorno. Scusa se ti è sembrato così, è che volevo darti tutte le informazioni sui canali prima.\n\nHai ragione, mi hai chiesto dei **Mix 17 e 18**. Procedo subito con l'analisi dettagliata di entrambi, così vediamo cosa ci sta succedendo.\n\nDammi un attimo per recuperare i dettagli completi di questi due mix.", + "timestamp": 1776009982.0498023, + "timestamp_str": "2026-04-12 16:06:22" + }, + { + "role": "user", + "content": "Ok", + "timestamp": 1776009998.6621764, + "timestamp_str": "2026-04-12 16:06:38" + }, + { + "role": "assistant", + "content": "✅ Fatto Nicola! Ho recuperato tutti i dettagli dei Mix 17 e 18.\n\nEcco un'analisi del **Mix 17 (Diretta)** e del **Mix 18 (Diretta)**. Sono configurati in modo identico, quindi l'analisi vale per entrambi. Il loro volume master è a -1 dB e sono attivi.\n\n---\n\n🤔 **Problemi Rilevati (molto importanti!):**\n\nIl problema più grande è che **molti canali sono spenti sul loro fader principale, ma stanno comunque inviando segnale a questi mix!** Questo significa che, anche se hai impostato un livello di send, non si sentiranno perché il canale è muto alla fonte.\n\n* ❌ **Vox3 (Canale 23)**: Spento, ma invia a **+5 dB**.\n* ❌ **Kick (Canale 9)**: Spento, ma invia a **+2 dB**.\n* ❌ **Vox2 (Canale 6)**: Spento, ma invia a **+1 dB**.\n* ❌ **Tom3 (Canale 13)**: Spento, ma invia a **-1.55 dB**.\n* ❌ **Tom 1 (Canale 11)**: Spento, ma invia a **-2 dB**.\n* ❌ **Snare (Canale 10)**: Spento, ma invia a **-8 dB**.\n* ❌ **Tast (Canale 20)**: Spento, ma invia a **-12 dB**.\n* ❌ **Pan dx (Canale 15)**: Spento, ma invia a **-20 dB**.\n* ❌ **Pan SX (Canale 14)**: Spento, ma invia a **-22 dB**.\n\n⚠️ **Suggerimenti per Migliorare l'equilibrio (una volta accesi i canali):**\n\n* **Gelato (Canale 2)** e **Vox3 (Canale 23)** sono i canali più forti, entrambi a **+5 dB**. Se uno dei due è la voce principale o l'elemento più importante, è un buon livello.\n* **Archetto (Canale 18)** è a **+4 dB**, quindi molto presente nel mix.\n* **Vox1 (Canale 5)** è a **+1 dB**.\n* **Chit cnt (Canale 24)** è a **-5 dB**. Se vuoi che la chitarra sia più in evidenza rispetto alle voci principali, potresti alzarla un po'.\n* I canali **PC (Canale 33 e 34)** sono a **-84 dB**, praticamente muti. Se devono essere presenti nel mix, vanno alzati parecchio.\n\n---\n\n**Domanda importante:** Vuoi che accenda tutti i canali che sono spenti ma stanno inviando segnale ai Mix 17 e 18? Altrimenti, non sentirai nulla da loro! Fammi sapere cosa vuoi fare. 🎤", + "timestamp": 1776010014.918493, + "timestamp_str": "2026-04-12 16:06:54" } ] } \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..79f483a --- /dev/null +++ b/main.py @@ -0,0 +1,249 @@ +# main.py + +import asyncio +import json +import time +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +import uvicorn + +# Importa la tua classe +# Assicurati che il nome file sia corretto, lo rinomino per chiarezza +from mixer_controller import TF5MixerController + +app = FastAPI(title="Yamaha TF5 Web Interface") + +app.mount("/static", StaticFiles(directory="static"), name="static") + +mixer_controller = None + +class ConnectionManager: + def __init__(self): + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def broadcast(self, message: str): + for connection in self.active_connections: + try: + await connection.send_text(message) + except Exception: + pass + +manager = ConnectionManager() + +@app.on_event("startup") +async def startup_event(): + global mixer_controller + mixer_controller = TF5MixerController(host="192.168.1.57") + + asyncio.create_task(broadcast_mixer_state()) + # === NUOVA RIGA === + asyncio.create_task(broadcast_meter_data()) # Avvia il broadcast dei meter + +@app.on_event("shutdown") +def shutdown_event(): + if mixer_controller: + mixer_controller.close() + +# NUOVA FUNZIONE PER I METER +async def broadcast_meter_data(): + """Invia i dati dei meter in tempo reale via WebSocket.""" + while True: + if mixer_controller and mixer_controller._is_connected.is_set() and manager.active_connections: + try: + # Esegui la funzione bloccante in un thread separato + snapshot = await asyncio.to_thread(mixer_controller.get_meter_snapshot) + await manager.broadcast(json.dumps({"type": "meter_update", "data": snapshot})) + except Exception as e: + print(f"Errore durante il broadcast dei meter: {e}") + + await asyncio.sleep(0.1) # Aggiorna 10 volte al secondo + +async def broadcast_mixer_state(): + """Controlla se la cache del mixer è cambiata e invia lo stato al WebSocket.""" + last_timestamp = 0 + while True: + if mixer_controller and mixer_controller._is_connected.is_set(): + with mixer_controller._cache_lock: + current_timestamp = mixer_controller._cache.get("timestamp", 0) + + if current_timestamp > last_timestamp: + state = { + "channels": mixer_controller.get_all_channels_summary().get("channels", []), + "steinch": mixer_controller.get_all_steinch_summary().get("channels", []), + "mixes": mixer_controller.get_all_mixes_summary().get("mixes", []), + "dcas": mixer_controller.get_all_dca_summary().get("dcas", []), + "fxrtn": mixer_controller.get_all_fxrtn_summary().get("channels", []), + "stereo": mixer_controller.get_stereo_info(1).get("level_db", -120), + "stereo_on": mixer_controller.get_stereo_info(1).get("on", False) + } + await manager.broadcast(json.dumps({"type": "state_update", "data": state})) + last_timestamp = current_timestamp + + await asyncio.sleep(0.1) + +@app.get("/") +async def get(): + with open("static/index.html", "r", encoding="utf-8") as f: + return HTMLResponse(f.read()) + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await manager.connect(websocket) + # Invia lo stato iniziale appena si connette + with mixer_controller._cache_lock: + mixer_controller._cache['timestamp'] = time.time() # Forza l'invio + + try: + while True: + data = await websocket.receive_text() + cmd = json.loads(data) + await handle_command(cmd) + except WebSocketDisconnect: + manager.disconnect(websocket) + +def get_actual_level(response_str: str, fallback_db: float) -> float: + try: + parts = response_str.strip().split() + for part in reversed(parts): + try: + val_int = int(part) + return val_int / 100.0 if val_int > -32768 else float('-inf') + except ValueError: + continue + except Exception: + pass + return fallback_db + +async def handle_command(cmd: dict): + action = cmd.get("action") + target_type = cmd.get("type") + value = cmd.get("value") + + raw_id = cmd.get("id", 1) + try: + ch_id = int(raw_id) + except ValueError: + ch_id = 1 + + # ========================================== + # 1. GESTIONE LIVELLI (MAIN FADERS) + # ========================================== + if action == "set_level": + val_float = float(value) + res = {} + if target_type == "channel": res = await asyncio.to_thread(mixer_controller.set_channel_level, ch_id, val_float) + elif target_type == "steinch": res = await asyncio.to_thread(mixer_controller.set_steinch_level, ch_id, val_float) + elif target_type == "mix": res = await asyncio.to_thread(mixer_controller.set_mix_level, ch_id, val_float) + elif target_type == "dca": res = await asyncio.to_thread(mixer_controller.set_dca_level, ch_id, val_float) + elif target_type == "fxrtn": res = await asyncio.to_thread(mixer_controller.set_fxrtn_level, ch_id, val_float) + elif target_type == "stereo": res = await asyncio.to_thread(mixer_controller.set_stereo_level, 1, val_float) + + actual_db = get_actual_level(res.get("response", ""), val_float) + + with mixer_controller._cache_lock: + cache_key = "channels" if target_type == "channel" else target_type + if target_type == "mix": cache_key = "mixes" + if target_type == "dca": cache_key = "dcas" + + str_id = str(ch_id) + if target_type == "stereo": + mixer_controller._cache.setdefault("stereo", {}).setdefault("1", {})["level_db"] = actual_db + elif cache_key in mixer_controller._cache and str_id in mixer_controller._cache[cache_key]: + mixer_controller._cache[cache_key][str_id]["level_db"] = actual_db + mixer_controller._cache['timestamp'] = time.time() + + # ========================================== + # 2. GESTIONE ON/OFF (MAIN FADERS) + # ========================================== + elif action == "set_on": + val_bool = bool(value) + if target_type == "channel": await asyncio.to_thread(mixer_controller.set_channel_on_off, ch_id, val_bool) + elif target_type == "steinch": await asyncio.to_thread(mixer_controller.set_steinch_on_off, ch_id, val_bool) + elif target_type == "mix": await asyncio.to_thread(mixer_controller.set_mix_on_off, ch_id, val_bool) + elif target_type == "dca": await asyncio.to_thread(mixer_controller.set_dca_on_off, ch_id, val_bool) + elif target_type == "fxrtn": await asyncio.to_thread(mixer_controller.set_fxrtn_on_off, ch_id, val_bool) + elif target_type == "stereo": await asyncio.to_thread(mixer_controller.set_stereo_on_off, 1, val_bool) + + with mixer_controller._cache_lock: + cache_key = "channels" if target_type == "channel" else target_type + if target_type == "mix": cache_key = "mixes" + if target_type == "dca": cache_key = "dcas" + + str_id = str(ch_id) + if target_type == "stereo": + mixer_controller._cache.setdefault("stereo", {}).setdefault("1", {})["on"] = val_bool + elif cache_key in mixer_controller._cache and str_id in mixer_controller._cache[cache_key]: + mixer_controller._cache[cache_key][str_id]["on"] = val_bool + mixer_controller._cache['timestamp'] = time.time() + + # ========================================== + # 3. GESTIONE "SENDS ON FADERS" (NUOVO!) + # ========================================== + elif action == "get_mix_sends": + target_mix = int(cmd.get("mix", 1)) + + # Funzione per interrogare velocemente tutti i 40 canali per un mix specifico + def fetch_sends(): + sends = [] + for ch in range(1, 41): # 40 Input Channels + try: + lvl_cmd = f"get MIXER:Current/InCh/ToMix/Level {ch-1} {target_mix-1}" + on_cmd = f"get MIXER:Current/InCh/ToMix/On {ch-1} {target_mix-1}" + + lvl_raw = mixer_controller._send_command(lvl_cmd) + on_raw = mixer_controller._send_command(on_cmd) + + lvl_db = mixer_controller._internal_to_level(mixer_controller._parse_value(lvl_raw)) + + # --- LA CORREZIONE È QUI --- + # Usa il sanitizzatore per convertire -Infinity in -120.0 + lvl_db = mixer_controller._sanitize_value(lvl_db) + + is_on = mixer_controller._parse_value(on_raw) == "1" + + # Prendi il nome dal canale master nella cache + name = f"CH {ch}" + with mixer_controller._cache_lock: + if str(ch) in mixer_controller._cache.get("channels", {}): + name = mixer_controller._cache["channels"][str(ch)]["name"] + + sends.append({ + "id": ch, + "name": name, + "level_db": lvl_db, + "on": is_on + }) + except Exception: + pass + return sends + + # Esegui in background per non bloccare FastAPI + sends_data = await asyncio.to_thread(fetch_sends) + await manager.broadcast(json.dumps({"type": "mix_sends_data", "mix": target_mix, "data": sends_data})) + + elif action == "set_send_level": + target_mix = int(cmd.get("mix", 1)) + val_float = float(value) + # Invia e recupera la risposta (per i log e l'allineamento) + res = await asyncio.to_thread(mixer_controller.set_channel_to_mix_level, ch_id, target_mix, val_float) + + # Logghiamo l'effettivo valore applicato dal mixer (OKm) + actual_db = get_actual_level(res.get("response", ""), val_float) + print(f"DEBUG: Mandata CH{ch_id} -> MIX{target_mix} impostata a {actual_db} dB") + + elif action == "set_send_on": + target_mix = int(cmd.get("mix", 1)) + val_bool = bool(value) + await asyncio.to_thread(mixer_controller.set_channel_to_mix_on_off, ch_id, target_mix, val_bool) + print(f"DEBUG: Mandata CH{ch_id} -> MIX{target_mix} {'ACCESA' if val_bool else 'SPENTA'}") + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/mixer_controller.py b/mixer_controller.py index aeb788f..bfc021b 100644 --- a/mixer_controller.py +++ b/mixer_controller.py @@ -32,6 +32,8 @@ class TF5MixerController: self._is_running = threading.Event() self._is_connected = threading.Event() + self._init_meter_state() + self.start() def start(self): @@ -63,15 +65,22 @@ class TF5MixerController: 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() + # Piccola pausa per assicurarsi che il buffer sia pulito + time.sleep(0.2) + + # Sottoscrivi DOPO il refresh (cache occupa il socket, meter devono aspettare) + self.subscribe_meters(interval_ms=100) + + self._socket_reader() # blocca qui finché connesso + except socket.error as e: print(f"🔥 Errore di connessione: {e}. Riprovo tra 5 secondi...") finally: + self.unsubscribe_meters() # tenta di cancellare prima di chiudere self._is_connected.clear() if self.socket: self.socket.close() @@ -112,6 +121,16 @@ class TF5MixerController: print(f"🔥 Errore di lettura dal socket: {e}") break + def _send_raw(self, command: str): + """Invia un comando senza attendere risposta.""" + if not self._is_connected.is_set(): + return + try: + print(f"SEND (raw): {command}") + self.socket.sendall((command + '\n').encode('utf-8')) + except socket.error as e: + print(f"⚠️ Errore invio raw: {e}") + def _handle_notify(self, message: str): """ Analizza un messaggio NOTIFY e aggiorna la cache in modo robusto. @@ -120,6 +139,11 @@ class TF5MixerController: print(f"RECV NOTIFY: {message}") parts = message.split() + + if len(parts) >= 2 and parts[1] == "mtrinfo": + self._handle_meter_notify(message) + return + if len(parts) < 2: return @@ -371,6 +395,68 @@ class TF5MixerController: finally: print(f" -> LOCK RILASCIATO.") + def _handle_prminfo_notify(self, message: str): + """ + Gestisce: NOTIFY prminfo ... + I valori sono: [ch0_type0, ch0_type1, ..., ch1_type0, ch1_type1, ...] + cioè interleaved per canale. + """ + parts = message.split() + # parts[0]="NOTIFY", parts[1]="prminfo", parts[2]=meter_id, parts[3:]=values + if len(parts) < 4: + return + try: + meter_id = int(parts[2]) + except ValueError: + return + + if meter_id not in self._METER_SUBSCRIPTIONS: + return + + info = self._METER_SUBSCRIPTIONS[meter_id] + key = info["key"] + num_ch = info["channels"] + num_types = info["types"] + + try: + values = [int(v) for v in parts[3:]] + except ValueError: + return + + expected = num_ch * num_types + if len(values) < expected: + return + + with self._meter_lock: + for ch in range(num_ch): + for t in range(num_types): + idx = ch * num_types + t + self._meter_data[key][t][ch] = values[idx] + + def _handle_meter_notify(self, message: str): + parts = message.split() + # NOTIFY mtrinfo ... + try: + meter_id = int(parts[2]) + except (IndexError, ValueError): + return + if meter_id not in self._METER_SUBSCRIPTIONS: + return + info = self._METER_SUBSCRIPTIONS[meter_id] + key = info["key"] + num_ch = info["channels"] + num_types = info["types"] + try: + values = [int(v) for v in parts[3:]] + except ValueError: + return + if len(values) < num_ch * num_types: + return + with self._meter_lock: + for ch in range(num_ch): + for t in range(num_types): + self._meter_data[key][t][ch] = values[ch * num_types + t] + def _send_command(self, command: str) -> str: """Invia un comando al mixer e attende la risposta.""" if not self._is_connected.wait(timeout=5): @@ -788,16 +874,20 @@ class TF5MixerController: def get_all_channels_summary(self) -> dict: if not self._is_cache_valid(): self.refresh_cache() with self._cache_lock: - channels_copy = list(self._cache.get("channels", {}).values()) + items_copy = list(self._cache.get("channels", {}).values()) cache_age = time.time() - self._cache.get("timestamp", 0) - channels = sorted([ - {"channel": info["channel"], "name": info["name"], "on": info["on"], - "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db")))} - for info in channels_copy - ], key=lambda x: x["channel"]) - 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)"} + + results = [] + for info in items_copy: + ch_num = info.get("channel", 0) + results.append({ + "channel": ch_num, + "name": info.get("name") or f"CH {ch_num}", + "on": info.get("on", False), + "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + }) + results.sort(key=lambda x: x["channel"]) + return {"status": "success", "total_channels": len(results), "channels": results, "cache_age_seconds": int(cache_age)} def mute_multiple_channels(self, channels: List[int]) -> dict: results = [self.set_channel_on_off(ch, False) for ch in channels] @@ -963,19 +1053,22 @@ class TF5MixerController: "message": f"StInCh {channel} → Mono send {'acceso' if state else 'spento'}", "response": response} def get_all_steinch_summary(self) -> dict: - """Restituisce il riepilogo di tutti i canali stereo di ingresso dalla cache.""" if not self._is_cache_valid(): self.refresh_cache() with self._cache_lock: - steinch_copy = list(self._cache.get("steinch", {}).values()) + items_copy = list(self._cache.get("steinch", {}).values()) cache_age = time.time() - self._cache.get("timestamp", 0) - channels = sorted([ - {"channel": info["channel"], "name": info["name"], "on": info["on"], - "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db")))} - for info in steinch_copy - ], key=lambda x: x["channel"]) - return {"status": "success", "total_channels": len(channels), "channels": channels, - "cache_age_seconds": int(cache_age), - "message": f"Riepilogo di {len(channels)} canali stereo (cache: {int(cache_age)}s fa)"} + + results = [] + for info in items_copy: + ch_num = info.get("channel", 0) + results.append({ + "channel": ch_num, + "name": info.get("name") or f"ST IN {ch_num}", + "on": info.get("on", False), + "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + }) + results.sort(key=lambda x: x["channel"]) + return {"status": "success", "total_channels": len(results), "channels": results, "cache_age_seconds": int(cache_age)} # ========================================================================= # FX RETURN CHANNELS (FxRtnCh) @@ -1090,19 +1183,22 @@ class TF5MixerController: "message": f"FxRtnCh {channel} → St pan impostato a {pan_value}", "response": response} def get_all_fxrtn_summary(self) -> dict: - """Restituisce il riepilogo di tutti i canali FX return dalla cache.""" if not self._is_cache_valid(): self.refresh_cache() with self._cache_lock: - fxrtn_copy = list(self._cache.get("fxrtn", {}).values()) + items_copy = list(self._cache.get("fxrtn", {}).values()) cache_age = time.time() - self._cache.get("timestamp", 0) - channels = sorted([ - {"channel": info["channel"], "name": info["name"], "on": info["on"], - "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db")))} - for info in fxrtn_copy - ], key=lambda x: x["channel"]) - return {"status": "success", "total_channels": len(channels), "channels": channels, - "cache_age_seconds": int(cache_age), - "message": f"Riepilogo di {len(channels)} canali FX return (cache: {int(cache_age)}s fa)"} + + results = [] + for info in items_copy: + ch_num = info.get("channel", 0) + results.append({ + "channel": ch_num, + "name": info.get("name") or f"FX RTN {ch_num}", + "on": info.get("on", False), + "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + }) + results.sort(key=lambda x: x["channel"]) + return {"status": "success", "total_channels": len(results), "channels": results, "cache_age_seconds": int(cache_age)} # ========================================================================= # DCA GROUPS @@ -1158,14 +1254,26 @@ class TF5MixerController: with self._cache_lock: dcas_copy = list(self._cache.get("dcas", {}).values()) cache_age = time.time() - self._cache.get("timestamp", 0) - dcas = sorted([ - {"dca": info["dca"], "name": info["name"], "on": info["on"], - "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db")))} - for info in dcas_copy - ], key=lambda x: x["dca"]) + + dcas = [] + for info in dcas_copy: + try: + # Usiamo .get() con valori di default per evitare KeyError + dca_num = info.get("dca", 0) + dcas.append({ + "dca": dca_num, + "name": info.get("name", f"DCA {dca_num}"), # Se manca il nome, usa "DCA 1" ecc. + "on": info.get("on", False), # Se manca lo stato, usa False + "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + }) + except Exception as e: + print(f"⚠️ Errore nel processare DCA info: {e}") + + dcas.sort(key=lambda x: x["dca"]) + return {"status": "success", "total_dcas": len(dcas), "dcas": dcas, "cache_age_seconds": int(cache_age), - "message": f"Riepilogo di {len(dcas)} DCA (cache: {int(cache_age)}s fa)"} + "message": f"Riepilogo di {len(dcas)} DCA"} # ========================================================================= # MIX BUSES @@ -1248,16 +1356,20 @@ class TF5MixerController: def get_all_mixes_summary(self) -> dict: if not self._is_cache_valid(): self.refresh_cache() with self._cache_lock: - mixes_copy = list(self._cache.get("mixes", {}).values()) + items_copy = list(self._cache.get("mixes", {}).values()) cache_age = time.time() - self._cache.get("timestamp", 0) - mixes = sorted([ - {"mix": info["mix"], "name": info["name"], "on": info["on"], - "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db")))} - for info in mixes_copy - ], key=lambda x: x["mix"]) - 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)"} + + results = [] + for info in items_copy: + mix_num = info.get("mix", 0) + results.append({ + "mix": mix_num, + "name": info.get("name") or f"MIX {mix_num}", + "on": info.get("on", False), + "level_db": self._sanitize_value(self._normalize_level_from_cache(info.get("level_db"))) + }) + results.sort(key=lambda x: x["mix"]) + return {"status": "success", "total_mixes": len(results), "mixes": results, "cache_age_seconds": int(cache_age)} def search_mixes_by_name(self, search_term: str) -> dict: if not self._is_cache_valid(): self.refresh_cache() @@ -1585,212 +1697,68 @@ class TF5MixerController: "message": f"Scena salvata nel banco {bank.upper()}{scene_number}.", "response": response} # ========================================================================= - # METER READING - # Riferimento prminfo: - # 2000 InCh 40ch 3 tipi: 0=PreHPF, 1=PreFader, 2=PostOn - # 2001 StInCh 4ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn - # 2002 FxRtnCh 4ch 2 tipi: 0=PreFader, 1=PostOn - # 2100 Mix 20ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn - # 2101 Mtrx 4ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn - # 2102 St 2ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn - # 2103 Mono 1ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn - # - # Il valore raw è un intero 0–127. - # Conversione: 0 = -inf dB, 1–127 lineare su scala ~-72..+18 dBFS. - # Formula Yamaha TF: dBFS = (raw / 127) * 90 - 72 (approssimazione lineare) - # Oppure si restituisce direttamente il raw per display proporzionale. + # METER READING (via prminfo subscription) # ========================================================================= - # Mappa dei meter disponibili - _METER_DEFS = { - "InCh": {"path": "MIXER:Current/Meter/InCh", "channels": 40, "types": ["PreHPF", "PreFader", "PostOn"]}, - "StInCh": {"path": "MIXER:Current/Meter/StInCh", "channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]}, - "FxRtnCh": {"path": "MIXER:Current/Meter/FxRtnCh", "channels": 4, "types": ["PreFader", "PostOn"]}, - "Mix": {"path": "MIXER:Current/Meter/Mix", "channels": 20, "types": ["PreEQ", "PreFader", "PostOn"]}, - "Mtrx": {"path": "MIXER:Current/Meter/Mtrx", "channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]}, - "St": {"path": "MIXER:Current/Meter/St", "channels": 2, "types": ["PreEQ", "PreFader", "PostOn"]}, - "Mono": {"path": "MIXER:Current/Meter/Mono", "channels": 1, "types": ["PreEQ", "PreFader", "PostOn"]}, + _METER_SUBSCRIPTIONS = { + 2000: {"key": "InCh", "channels": 40, "types": 3}, + 2001: {"key": "StInCh", "channels": 4, "types": 3}, + 2002: {"key": "FxRtnCh", "channels": 4, "types": 2}, + 2100: {"key": "Mix", "channels": 20, "types": 3}, + 2101: {"key": "Mtrx", "channels": 4, "types": 3}, + 2102: {"key": "St", "channels": 2, "types": 3}, + 2103: {"key": "Mono", "channels": 1, "types": 3}, } + # Dizionario meter: { "InCh": [[ch1t0, ch1t1, ch1t2], [ch2t0,...], ...], ... } + # Oppure più semplice: { "InCh": {type_idx: [val_ch1, val_ch2, ...]}, ... } + _meter_data: dict = {} + _meter_lock: threading.Lock = None # verrà inizializzato in __init__ + + def _init_meter_state(self): + """Inizializza la struttura dati per i meter.""" + self._meter_lock = threading.Lock() + self._meter_data = {} + for info in self._METER_SUBSCRIPTIONS.values(): + key = info["key"] + self._meter_data[key] = { + t: [0] * info["channels"] for t in range(info["types"]) + } + + def subscribe_meters(self, interval_ms: int = 100): + # Il comando corretto per i meter Yamaha TF è "mtrinfo" + for meter_id in self._METER_SUBSCRIPTIONS: + self._send_raw(f"mtrinfo {meter_id} {interval_ms}") + time.sleep(0.05) + + def unsubscribe_meters(self): + for meter_id in self._METER_SUBSCRIPTIONS: + self._send_raw(f"mtrinfo {meter_id} 0") + + def get_meter_snapshot(self) -> dict: + """Restituisce l'ultimo snapshot meter ricevuto.""" + with self._meter_lock: + # Costruisce il formato atteso dal frontend + snapshot = {} + for meter_id, info in self._METER_SUBSCRIPTIONS.items(): + key = info["key"] + frontend_key = key.lower() + # Il frontend si aspetta { readings: [{channel, raw, level_db}] } + type_idx = info["types"] - 1 # Usiamo l'ultimo tipo (PostOn) + readings = [] + for ch_idx, raw in enumerate(self._meter_data[key][type_idx]): + readings.append({ + "channel": ch_idx + 1, + "raw": raw, + "level_db": self._meter_raw_to_db(raw) + }) + snapshot[frontend_key] = { + "readings": readings, + "type_name": "PostOn" + } + return snapshot + def _meter_raw_to_db(self, raw: int) -> float: - """ - Converte il valore raw del meter (0-127) in dBFS approssimato. - Scala Yamaha TF: 127 = +18 dBFS, 76 = 0 dBFS, 1 = -72 dBFS, 0 = -inf. - """ if raw <= 0: return float('-inf') - # Interpolazione lineare: 0..127 → -72..+18 dBFS (range 90 dB) - return round((raw / 127.0) * 90.0 - 72.0, 1) - - def _parse_meter_response(self, response: str) -> Optional[int]: - """Estrae il valore intero raw dal response di un comando get meter.""" - try: - first_line = response.split('\n')[0].strip() - parts = first_line.split() - if parts and parts[0] == "OK": - return int(parts[-1]) - except (ValueError, IndexError): - pass - return None - - def get_meter(self, section: str, channel: int, meter_type: int = 2) -> dict: - """ - Legge il livello meter di un singolo canale. - - Args: - section: Sezione del mixer: "InCh", "StInCh", "FxRtnCh", - "Mix", "Mtrx", "St", "Mono" - channel: Numero canale (1-based) - meter_type: Indice del tipo meter (0-based, vedi _METER_DEFS per i nomi) - - Returns: - dict con raw (0-127), level_db (dBFS), section, channel, type_name - """ - if section not in self._METER_DEFS: - return {"status": "error", - "message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"} - - defn = self._METER_DEFS[section] - num_ch = defn["channels"] - num_types = len(defn["types"]) - - if not 1 <= channel <= num_ch: - return {"status": "error", "message": f"Canale {section} deve essere tra 1 e {num_ch}"} - if not 0 <= meter_type < num_types: - return {"status": "error", - "message": f"Tipo meter deve essere tra 0 e {num_types - 1} per {section}: {defn['types']}"} - - ch_idx = channel - 1 - command = f"get {defn['path']} {ch_idx} {meter_type}" - response = self._send_command(command) - raw = self._parse_meter_response(response) - - if raw is None: - return {"status": "error", "message": f"Risposta non valida: {response}"} - - level_db = self._meter_raw_to_db(raw) - type_name = defn["types"][meter_type] - - return { - "status": "success", - "section": section, - "channel": channel, - "type": meter_type, - "type_name": type_name, - "raw": raw, - "level_db": level_db, - "message": f"{section} ch{channel} [{type_name}]: {raw}/127 → {level_db} dBFS" - } - - def get_all_meters(self, section: str, meter_type: int = 2) -> dict: - """ - Legge tutti i canali meter di una sezione. - - Args: - section: "InCh", "StInCh", "FxRtnCh", "Mix", "Mtrx", "St", "Mono" - meter_type: Indice tipo meter (0-based) - - Returns: - dict con lista di letture meter per tutti i canali della sezione - """ - if section not in self._METER_DEFS: - return {"status": "error", - "message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"} - - defn = self._METER_DEFS[section] - num_ch = defn["channels"] - num_types = len(defn["types"]) - - if not 0 <= meter_type < num_types: - return {"status": "error", - "message": f"Tipo meter deve essere tra 0 e {num_types - 1} per {section}: {defn['types']}"} - - type_name = defn["types"][meter_type] - readings = [] - - for ch in range(1, num_ch + 1): - ch_idx = ch - 1 - command = f"get {defn['path']} {ch_idx} {meter_type}" - response = self._send_command(command) - raw = self._parse_meter_response(response) - if raw is not None: - level_db = self._meter_raw_to_db(raw) - readings.append({ - "channel": ch, - "raw": raw, - "level_db": level_db - }) - else: - readings.append({ - "channel": ch, - "raw": None, - "level_db": None, - "error": response - }) - - return { - "status": "success", - "section": section, - "type": meter_type, - "type_name": type_name, - "total_channels": num_ch, - "readings": readings, - "message": f"Meter {section} [{type_name}]: {num_ch} canali letti" - } - - def get_meter_snapshot(self, meter_type: int = 2) -> dict: - """ - Legge un snapshot dei meter di tutte le sezioni principali. - - Args: - meter_type: Tipo meter da usare per tutte le sezioni. - Nota: FxRtnCh ha solo 2 tipi (0-1), se si passa 2 - viene automaticamente usato il tipo 1 (PostOn). - - Returns: - dict con un sotto-dict per ogni sezione, contenente le letture. - """ - snapshot = {} - for section, defn in self._METER_DEFS.items(): - # Clamp meter_type al massimo disponibile per la sezione - safe_type = min(meter_type, len(defn["types"]) - 1) - result = self.get_all_meters(section, safe_type) - snapshot[section] = result - - sections_ok = sum(1 for r in snapshot.values() if r["status"] == "success") - return { - "status": "success", - "meter_type_requested": meter_type, - "sections": snapshot, - "message": f"Snapshot meter: {sections_ok}/{len(self._METER_DEFS)} sezioni lette" - } - - def get_meter_types(self, section: str = None) -> dict: - """ - Restituisce i tipi di meter disponibili per una sezione (o tutte). - - Args: - section: Nome sezione opzionale. Se None, restituisce tutte. - """ - if section: - if section not in self._METER_DEFS: - return {"status": "error", - "message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"} - defn = self._METER_DEFS[section] - return { - "status": "success", - "section": section, - "channels": defn["channels"], - "types": {i: name for i, name in enumerate(defn["types"])} - } - else: - return { - "status": "success", - "sections": { - sec: { - "channels": d["channels"], - "types": {i: name for i, name in enumerate(d["types"])} - } - for sec, d in self._METER_DEFS.items() - } - } \ No newline at end of file + return round((raw / 127.0) * 90.0 - 72.0, 1) \ No newline at end of file diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..27d9d26 --- /dev/null +++ b/static/index.html @@ -0,0 +1,389 @@ + + + + + + + TF5 Web Mixer + + + + + + +
+ +
+

YAMAHA TF5 Web Controller

+
+ + + + + {{ wsConnected ? 'Connesso' : 'Disconnesso' }} +
+
+ + + + + +
+ SELEZIONA MIX: + +
Caricamento fader...
+
+ + +
+ + +
+ +
+ Nessun fader disponibile in questa vista. +
+ + +
+ +
+ {{ fader.name }} +
+ + + + +
+ +
+
+
+ +
+
+ +100-20-60-∞ +
+ +
+
+ +
+ {{ formatDb(fader.level_db) }} +
+
{{ getFaderLabel(fader.id) }}
+
+ +
 
+
+ + +
+ + +
+
+ MIX {{ activeMixId }} + {{ currentMixMaster.name }} +
+ + +
+
+
+ +
+
+
{{ formatDb(currentMixMaster.level_db) }}
+
MASTER
+
+ + +
+
+ Stereo L/R
+ + +
+
+
+ +
+
+
{{ formatDb(state.stereo) }}
+
MAIN
+
+ +
+
+
+ + + + + \ No newline at end of file