Compare commits
11 Commits
fdba2e9afc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deee299af5 | ||
|
|
1975a6f1d0 | ||
|
|
729cb649fc | ||
|
|
14512aa3f4 | ||
|
|
4d5119ab5a | ||
|
|
9173aed76f | ||
|
|
3f09ff2bd6 | ||
|
|
221e1ad318 | ||
|
|
4246d13339 | ||
|
|
86fa13a1a0 | ||
|
|
77bd4fabb6 |
@@ -18,7 +18,7 @@
|
|||||||
"channel": 3,
|
"channel": 3,
|
||||||
"name": "Talkback",
|
"name": "Talkback",
|
||||||
"on": false,
|
"on": false,
|
||||||
"level_db": null,
|
"level_db": -60.0,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
@@ -31,15 +31,15 @@
|
|||||||
"5": {
|
"5": {
|
||||||
"channel": 5,
|
"channel": 5,
|
||||||
"name": "Vox1",
|
"name": "Vox1",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -1.1,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"6": {
|
"6": {
|
||||||
"channel": 6,
|
"channel": 6,
|
||||||
"name": "Vox2",
|
"name": "Vox2",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -0.15,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"7": {
|
"7": {
|
||||||
@@ -59,50 +59,50 @@
|
|||||||
"9": {
|
"9": {
|
||||||
"channel": 9,
|
"channel": 9,
|
||||||
"name": "Kick",
|
"name": "Kick",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -5.2,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"10": {
|
"10": {
|
||||||
"channel": 10,
|
"channel": 10,
|
||||||
"name": "Snare",
|
"name": "Snare",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -9.95,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"11": {
|
"11": {
|
||||||
"channel": 11,
|
"channel": 11,
|
||||||
"name": "Tom 1",
|
"name": "Tom 1",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -16.6,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"12": {
|
"12": {
|
||||||
"channel": 12,
|
"channel": 12,
|
||||||
"name": "Tom 2",
|
"name": "Tom 2",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -18.0,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"13": {
|
"13": {
|
||||||
"channel": 13,
|
"channel": 13,
|
||||||
"name": "Tom3",
|
"name": "Tom3",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -24.0,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"14": {
|
"14": {
|
||||||
"channel": 14,
|
"channel": 14,
|
||||||
"name": "Pan SX",
|
"name": "Pan SX",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -15.08,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"15": {
|
"15": {
|
||||||
"channel": 15,
|
"channel": 15,
|
||||||
"name": "Pan dx",
|
"name": "Pan dx",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -14.68,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"16": {
|
"16": {
|
||||||
@@ -123,21 +123,21 @@
|
|||||||
"channel": 18,
|
"channel": 18,
|
||||||
"name": "Archetto",
|
"name": "Archetto",
|
||||||
"on": false,
|
"on": false,
|
||||||
"level_db": null,
|
"level_db": -76.0,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"19": {
|
"19": {
|
||||||
"channel": 19,
|
"channel": 19,
|
||||||
"name": "Vox 5",
|
"name": "Vox 5",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -0.1,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"20": {
|
"20": {
|
||||||
"channel": 20,
|
"channel": 20,
|
||||||
"name": "Tast",
|
"name": "Tast",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": 0.0,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"21": {
|
"21": {
|
||||||
@@ -157,15 +157,15 @@
|
|||||||
"23": {
|
"23": {
|
||||||
"channel": 23,
|
"channel": 23,
|
||||||
"name": " Vox3",
|
"name": " Vox3",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -0.2,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"24": {
|
"24": {
|
||||||
"channel": 24,
|
"channel": 24,
|
||||||
"name": "Chit cnt",
|
"name": "Chit cnt",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": -1.75,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
"25": {
|
"25": {
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
"27": {
|
"27": {
|
||||||
"channel": 27,
|
"channel": 27,
|
||||||
"name": "Vox 4",
|
"name": "Vox 4",
|
||||||
"on": true,
|
"on": false,
|
||||||
"level_db": null,
|
"level_db": null,
|
||||||
"pan": 0
|
"pan": 0
|
||||||
},
|
},
|
||||||
@@ -228,14 +228,14 @@
|
|||||||
"channel": 33,
|
"channel": 33,
|
||||||
"name": "PC",
|
"name": "PC",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -20.4,
|
"level_db": -17.5,
|
||||||
"pan": -63
|
"pan": 0
|
||||||
},
|
},
|
||||||
"34": {
|
"34": {
|
||||||
"channel": 34,
|
"channel": 34,
|
||||||
"name": "PC",
|
"name": "PC",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -20.4,
|
"level_db": -17.5,
|
||||||
"pan": 63
|
"pan": 63
|
||||||
},
|
},
|
||||||
"35": {
|
"35": {
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
"mix": 1,
|
"mix": 1,
|
||||||
"name": "Aux 1",
|
"name": "Aux 1",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -1.55
|
"level_db": -11.1
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"mix": 2,
|
"mix": 2,
|
||||||
@@ -298,37 +298,37 @@
|
|||||||
"mix": 3,
|
"mix": 3,
|
||||||
"name": "Aux 3",
|
"name": "Aux 3",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -0.5
|
"level_db": 0.35
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
"mix": 4,
|
"mix": 4,
|
||||||
"name": "Aux 4",
|
"name": "Aux 4",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -0.6
|
"level_db": 0.0
|
||||||
},
|
},
|
||||||
"5": {
|
"5": {
|
||||||
"mix": 5,
|
"mix": 5,
|
||||||
"name": "batteria",
|
"name": "batteria",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": 1.35
|
"level_db": -4.9
|
||||||
},
|
},
|
||||||
"6": {
|
"6": {
|
||||||
"mix": 6,
|
"mix": 6,
|
||||||
"name": "Aux 6",
|
"name": "Aux 6",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -6.65
|
"level_db": -9.3
|
||||||
},
|
},
|
||||||
"7": {
|
"7": {
|
||||||
"mix": 7,
|
"mix": 7,
|
||||||
"name": "Aux 7",
|
"name": "Aux 7",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -2.7
|
"level_db": -6.35
|
||||||
},
|
},
|
||||||
"8": {
|
"8": {
|
||||||
"mix": 8,
|
"mix": 8,
|
||||||
"name": "Aux 8",
|
"name": "Aux 8",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": -2.65
|
"level_db": -9.0
|
||||||
},
|
},
|
||||||
"9": {
|
"9": {
|
||||||
"mix": 9,
|
"mix": 9,
|
||||||
@@ -382,13 +382,13 @@
|
|||||||
"mix": 17,
|
"mix": 17,
|
||||||
"name": "Diretta",
|
"name": "Diretta",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": 0.0
|
"level_db": -1.0
|
||||||
},
|
},
|
||||||
"18": {
|
"18": {
|
||||||
"mix": 18,
|
"mix": 18,
|
||||||
"name": "Diretta",
|
"name": "Diretta",
|
||||||
"on": true,
|
"on": true,
|
||||||
"level_db": 0.0
|
"level_db": -1.0
|
||||||
},
|
},
|
||||||
"19": {
|
"19": {
|
||||||
"mix": 19,
|
"mix": 19,
|
||||||
@@ -403,5 +403,12 @@
|
|||||||
"level_db": 6.0
|
"level_db": 6.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timestamp": 1762193941.3288717
|
"timestamp": 1770577942.7806144,
|
||||||
|
"steinch": {},
|
||||||
|
"fxrtn": {},
|
||||||
|
"dcas": {},
|
||||||
|
"matrices": {},
|
||||||
|
"stereo": {},
|
||||||
|
"mono": {},
|
||||||
|
"mute_masters": {}
|
||||||
}
|
}
|
||||||
4
.tf5_mixer_cache/mixer_ip.json
Normal file
4
.tf5_mixer_cache/mixer_ip.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"ip": "192.168.1.61",
|
||||||
|
"timestamp": 1772477899.4678419
|
||||||
|
}
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Dockerfile
|
||||||
|
|
||||||
|
# 1. Usa un'immagine Python ufficiale e leggera come base
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# 2. Imposta una directory di lavoro all'interno del container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 3. Copia il file delle dipendenze e installale
|
||||||
|
# Questo passaggio viene fatto prima per sfruttare la cache di Docker.
|
||||||
|
# Se requirements.txt non cambia, questo strato non verrà ricostruito.
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 4. Copia tutti i file del progetto nella directory di lavoro del container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 5. Comando da eseguire quando il container viene avviato
|
||||||
|
CMD ["python", "telegram_bot.py"]
|
||||||
Binary file not shown.
Binary file not shown.
407
cache/mixer_state.json
vendored
Normal file
407
cache/mixer_state.json
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
249
config.py
249
config.py
@@ -1,13 +1,252 @@
|
|||||||
|
import socket
|
||||||
|
import ipaddress
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import time
|
||||||
|
|
||||||
# Configurazione mixer
|
# Configurazione mixer
|
||||||
DEFAULT_HOST = "192.168.1.62"
|
|
||||||
DEFAULT_PORT = 49280
|
DEFAULT_PORT = 49280
|
||||||
TF5_INPUT_CHANNELS = 40
|
TF5_INPUT_CHANNELS = 40
|
||||||
TF5_MIX_BUSSES = 20
|
TF5_MIX_BUSSES = 20
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
# Configurazione cache
|
# Configurazione cache
|
||||||
CACHE_DIR = Path(".tf5_mixer_cache")
|
CACHE_DIR = Path(".tf5_mixer_cache")
|
||||||
CACHE_FILE = CACHE_DIR / "channels_cache.json"
|
CACHE_FILE = CACHE_DIR / "channels_cache.json"
|
||||||
CACHE_DURATION = 3600 # 60 minuti in secondi
|
IP_CACHE_FILE = CACHE_DIR / "mixer_ip.json"
|
||||||
|
CACHE_DURATION = 3600 # 60 minuti in secondi
|
||||||
|
IP_CACHE_DURATION = 300 # 5 minuti per l'IP
|
||||||
|
|
||||||
|
def log(message: str, level: str = "INFO"):
|
||||||
|
"""Stampa log formattato con timestamp"""
|
||||||
|
timestamp = time.strftime("%H:%M:%S")
|
||||||
|
symbols = {
|
||||||
|
"INFO": "ℹ️",
|
||||||
|
"SUCCESS": "✅",
|
||||||
|
"ERROR": "❌",
|
||||||
|
"WARNING": "⚠️",
|
||||||
|
"SEARCH": "🔍",
|
||||||
|
"NETWORK": "🌐",
|
||||||
|
"CACHE": "📍",
|
||||||
|
"TIME": "⏱️"
|
||||||
|
}
|
||||||
|
symbol = symbols.get(level, "•")
|
||||||
|
print(f"[{timestamp}] {symbol} {message}")
|
||||||
|
|
||||||
|
def get_local_network() -> str:
|
||||||
|
"""Ottiene la rete locale del sistema"""
|
||||||
|
log("Rilevamento rete locale in corso...", "NETWORK")
|
||||||
|
try:
|
||||||
|
# Crea un socket per ottenere l'IP locale
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.connect(("8.8.8.8", 80))
|
||||||
|
local_ip = s.getsockname()[0]
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
# Converte in network (es: 192.168.1.0/24)
|
||||||
|
ip_parts = local_ip.split('.')
|
||||||
|
network = f"{ip_parts[0]}.{ip_parts[1]}.{ip_parts[2]}.0/24"
|
||||||
|
|
||||||
|
log(f"IP locale rilevato: {local_ip}", "INFO")
|
||||||
|
log(f"Rete da scansionare: {network}", "NETWORK")
|
||||||
|
return network
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Errore nel rilevare la rete locale: {e}", "ERROR")
|
||||||
|
network = "192.168.1.0/24"
|
||||||
|
log(f"Uso rete di fallback: {network}", "WARNING")
|
||||||
|
return network
|
||||||
|
|
||||||
|
def check_port(ip: str, port: int, timeout: float = 0.5) -> bool:
|
||||||
|
"""Controlla se una porta è aperta su un IP"""
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
result = sock.connect_ex((ip, port))
|
||||||
|
sock.close()
|
||||||
|
return result == 0
|
||||||
|
except Exception as e:
|
||||||
|
# Log solo per debug, commentabile se troppo verbose
|
||||||
|
# log(f"Errore su {ip}: {e}", "ERROR")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def scan_network_for_mixer(port: int = DEFAULT_PORT, max_workers: int = 50) -> Optional[str]:
|
||||||
|
"""Scansiona la rete locale per trovare il mixer"""
|
||||||
|
log(f"Ricerca mixer sulla porta {port}...", "SEARCH")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
network = get_local_network()
|
||||||
|
|
||||||
|
# Genera lista di IP da scansionare
|
||||||
|
ip_network = ipaddress.ip_network(network, strict=False)
|
||||||
|
total_hosts = sum(1 for _ in ip_network.hosts())
|
||||||
|
log(f"Indirizzi da scansionare: {total_hosts}", "INFO")
|
||||||
|
log(f"Thread paralleli: {max_workers}", "INFO")
|
||||||
|
|
||||||
|
found_ip = None
|
||||||
|
scanned = 0
|
||||||
|
|
||||||
|
# Scansione parallela per velocità
|
||||||
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
|
future_to_ip = {
|
||||||
|
executor.submit(check_port, str(ip), port): str(ip)
|
||||||
|
for ip in ip_network.hosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Scansione in corso...", "SEARCH")
|
||||||
|
|
||||||
|
for future in as_completed(future_to_ip):
|
||||||
|
ip = future_to_ip[future]
|
||||||
|
scanned += 1
|
||||||
|
|
||||||
|
# Log di progresso ogni 50 IP
|
||||||
|
if scanned % 50 == 0:
|
||||||
|
log(f"Progresso: {scanned}/{total_hosts} IP scansionati", "INFO")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if future.result():
|
||||||
|
found_ip = ip
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
log(f"MIXER TROVATO su {ip}:{port}", "SUCCESS")
|
||||||
|
log(f"Tempo di scansione: {elapsed:.2f}s", "TIME")
|
||||||
|
log(f"IP scansionati: {scanned}/{total_hosts}", "INFO")
|
||||||
|
# Cancella i task rimanenti
|
||||||
|
executor.shutdown(wait=False, cancel_futures=True)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Errore durante scansione di {ip}: {e}", "ERROR")
|
||||||
|
|
||||||
|
if not found_ip:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
log(f"Nessun mixer trovato sulla porta {port}", "ERROR")
|
||||||
|
log(f"Tempo totale di scansione: {elapsed:.2f}s", "TIME")
|
||||||
|
log(f"IP scansionati: {scanned}/{total_hosts}", "INFO")
|
||||||
|
|
||||||
|
return found_ip
|
||||||
|
|
||||||
|
def load_cached_ip() -> Optional[str]:
|
||||||
|
"""Carica l'IP dalla cache se valido"""
|
||||||
|
log("Controllo cache IP...", "CACHE")
|
||||||
|
|
||||||
|
if not IP_CACHE_FILE.exists():
|
||||||
|
log("Nessuna cache trovata", "INFO")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(IP_CACHE_FILE, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
cached_ip = data.get('ip')
|
||||||
|
cache_time = data.get('timestamp', 0)
|
||||||
|
age = time.time() - cache_time
|
||||||
|
|
||||||
|
log(f"Cache trovata: {cached_ip} (età: {int(age)}s)", "CACHE")
|
||||||
|
|
||||||
|
# Controlla se la cache è ancora valida
|
||||||
|
if age < IP_CACHE_DURATION:
|
||||||
|
log(f"Verifica raggiungibilità di {cached_ip}...", "CACHE")
|
||||||
|
# Verifica che l'IP sia ancora valido
|
||||||
|
if cached_ip and check_port(cached_ip, DEFAULT_PORT, timeout=1.0):
|
||||||
|
log(f"IP dalla cache valido: {cached_ip}", "SUCCESS")
|
||||||
|
return cached_ip
|
||||||
|
else:
|
||||||
|
log(f"IP dalla cache non raggiungibile", "WARNING")
|
||||||
|
else:
|
||||||
|
log(f"Cache scaduta (max {IP_CACHE_DURATION}s)", "WARNING")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Errore nel leggere cache IP: {e}", "ERROR")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_ip_to_cache(ip: str):
|
||||||
|
"""Salva l'IP nella cache"""
|
||||||
|
log(f"Salvataggio IP in cache: {ip}", "CACHE")
|
||||||
|
CACHE_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(IP_CACHE_FILE, 'w') as f:
|
||||||
|
json.dump({
|
||||||
|
'ip': ip,
|
||||||
|
'timestamp': time.time()
|
||||||
|
}, f, indent=2)
|
||||||
|
log(f"Cache salvata: {IP_CACHE_FILE}", "SUCCESS")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Errore nel salvare cache IP: {e}", "ERROR")
|
||||||
|
|
||||||
|
def get_mixer_ip() -> str:
|
||||||
|
"""
|
||||||
|
Ottiene l'IP del mixer con auto-discovery:
|
||||||
|
1. Controlla cache
|
||||||
|
2. Scansiona la rete
|
||||||
|
3. Fallback su IP predefinito
|
||||||
|
"""
|
||||||
|
log("=== INIZIO RICERCA MIXER ===", "INFO")
|
||||||
|
|
||||||
|
# Prova con la cache
|
||||||
|
cached_ip = load_cached_ip()
|
||||||
|
if cached_ip:
|
||||||
|
log("=== RICERCA COMPLETATA (CACHE) ===", "SUCCESS")
|
||||||
|
return cached_ip
|
||||||
|
|
||||||
|
# Scansiona la rete
|
||||||
|
log("Avvio scansione di rete...", "SEARCH")
|
||||||
|
discovered_ip = scan_network_for_mixer()
|
||||||
|
if discovered_ip:
|
||||||
|
save_ip_to_cache(discovered_ip)
|
||||||
|
log("=== RICERCA COMPLETATA (DISCOVERY) ===", "SUCCESS")
|
||||||
|
return discovered_ip
|
||||||
|
|
||||||
|
# Fallback
|
||||||
|
fallback_ip = "192.168.1.57"
|
||||||
|
log(f"Uso IP predefinito di fallback: {fallback_ip}", "WARNING")
|
||||||
|
log("=== RICERCA COMPLETATA (FALLBACK) ===", "WARNING")
|
||||||
|
return fallback_ip
|
||||||
|
|
||||||
|
# Variabile globale per l'host (si aggiorna automaticamente)
|
||||||
|
DEFAULT_HOST = None
|
||||||
|
|
||||||
|
def get_host() -> str:
|
||||||
|
"""Ottiene l'host del mixer (con lazy loading)"""
|
||||||
|
global DEFAULT_HOST
|
||||||
|
if DEFAULT_HOST is None:
|
||||||
|
log("Host mixer non inizializzato, avvio discovery...", "INFO")
|
||||||
|
DEFAULT_HOST = get_mixer_ip()
|
||||||
|
else:
|
||||||
|
log(f"Host mixer già inizializzato: {DEFAULT_HOST}", "INFO")
|
||||||
|
return DEFAULT_HOST
|
||||||
|
|
||||||
|
def refresh_mixer_ip():
|
||||||
|
"""Forza un refresh dell'IP del mixer"""
|
||||||
|
global DEFAULT_HOST
|
||||||
|
log("=== REFRESH FORZATO IP MIXER ===", "INFO")
|
||||||
|
log("Cancellazione cache esistente...", "CACHE")
|
||||||
|
|
||||||
|
# Rimuovi cache
|
||||||
|
if IP_CACHE_FILE.exists():
|
||||||
|
try:
|
||||||
|
IP_CACHE_FILE.unlink()
|
||||||
|
log("Cache rimossa", "SUCCESS")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Errore rimozione cache: {e}", "ERROR")
|
||||||
|
|
||||||
|
# Nuova scansione
|
||||||
|
DEFAULT_HOST = scan_network_for_mixer()
|
||||||
|
if DEFAULT_HOST:
|
||||||
|
save_ip_to_cache(DEFAULT_HOST)
|
||||||
|
log(f"Nuovo IP impostato: {DEFAULT_HOST}", "SUCCESS")
|
||||||
|
else:
|
||||||
|
log("Refresh fallito, mantengo IP precedente", "ERROR")
|
||||||
|
|
||||||
|
log("=== REFRESH COMPLETATO ===", "INFO")
|
||||||
|
return DEFAULT_HOST
|
||||||
|
|
||||||
|
# Test rapido
|
||||||
|
if __name__ == "__main__":
|
||||||
|
log("=== TEST AUTO-DISCOVERY MIXER ===", "INFO")
|
||||||
|
host = get_host()
|
||||||
|
log(f"Host finale: {host}", "SUCCESS")
|
||||||
|
|
||||||
|
# Test refresh
|
||||||
|
# log("\n=== TEST REFRESH ===", "INFO")
|
||||||
|
# refresh_mixer_ip()
|
||||||
21
conversations/1073121360.json
Normal file
21
conversations/1073121360.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"user_id": 1073121360,
|
||||||
|
"username": "Ingdavidfeliz",
|
||||||
|
"last_updated": 1762197549.52935,
|
||||||
|
"last_updated_str": "2025-11-03 19:19:09",
|
||||||
|
"message_count": 2,
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "Alzami il basso",
|
||||||
|
"timestamp": 1762197547.3495104,
|
||||||
|
"timestamp_str": "2025-11-03 19:19:07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Ciao David! Per \"alzami il basso\", intendi nel tuo monitor personale o in un altro mix? Ho bisogno di sapere in quale mix vuoi che alzi il basso.",
|
||||||
|
"timestamp": 1762197549.5293229,
|
||||||
|
"timestamp_str": "2025-11-03 19:19:09"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
93
conversations/309700430.json
Normal file
93
conversations/309700430.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
conversations/436176546.json
Normal file
21
conversations/436176546.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,87 +1,135 @@
|
|||||||
{
|
{
|
||||||
"user_id": 4762864,
|
"user_id": 4762864,
|
||||||
"username": "unnikked",
|
"username": "unnikked",
|
||||||
"last_updated": 1762194065.7709692,
|
"last_updated": 1770578542.7348943,
|
||||||
"last_updated_str": "2025-11-03 19:21:05",
|
"last_updated_str": "2026-02-08 19:22:22",
|
||||||
"message_count": 13,
|
"message_count": 21,
|
||||||
"history": [
|
"history": [
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Me lo spegni il gelato",
|
"content": "Controlla ora",
|
||||||
"timestamp": 1761595234.0849943,
|
"timestamp": 1769455908.2156205,
|
||||||
"timestamp_str": "2025-10-27 21:00:34"
|
"timestamp_str": "2026-01-26 19:31:48"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Fatto! Canale \"Gelato\" spento. ✅",
|
"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": 1761595239.1608367,
|
"timestamp": 1769455917.246521,
|
||||||
"timestamp_str": "2025-10-27 21:00:39"
|
"timestamp_str": "2026-01-26 19:31:57"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Mi accendi il gelato.",
|
"content": "ciao",
|
||||||
"timestamp": 1761595383.8880353,
|
"timestamp": 1769766889.7665708,
|
||||||
"timestamp_str": "2025-10-27 21:03:03"
|
"timestamp_str": "2026-01-30 09:54:49"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Certo! Gelato acceso! 🍦✅",
|
"content": "Ciao Nicola! Tutto bene? Dimmi pure se hai bisogno di qualcosa con il mixer! 🎛️🎤",
|
||||||
"timestamp": 1761595387.27931,
|
"timestamp": 1769766891.7504349,
|
||||||
"timestamp_str": "2025-10-27 21:03:07"
|
"timestamp_str": "2026-01-30 09:54:51"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Apaga el helado. Cappuccino.",
|
"content": "è acceso?",
|
||||||
"timestamp": 1761595426.0112157,
|
"timestamp": 1769766900.066535,
|
||||||
"timestamp_str": "2025-10-27 21:03:46"
|
"timestamp_str": "2026-01-30 09:55:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Gelato spento! ✅\nNon ho trovato nessun canale chiamato \"Cappuccino\", sei sicuro del nome? 🤔",
|
"content": "Cosa intendi? Un canale, un mix, o qualcos'altro? 🧐",
|
||||||
"timestamp": 1761595431.5373232,
|
"timestamp": 1769766901.4737165,
|
||||||
"timestamp_str": "2025-10-27 21:03:51"
|
"timestamp_str": "2026-01-30 09:55:01"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Okay, aprimi il gelato allora!",
|
"content": "proprio il mixer, lo riesci a raggiungere?",
|
||||||
"timestamp": 1762193935.418264,
|
"timestamp": 1769766912.3007145,
|
||||||
"timestamp_str": "2025-11-03 19:18:55"
|
"timestamp_str": "2026-01-30 09:55:12"
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "Gli alzi il gelato?",
|
|
||||||
"timestamp": 1762193958.1497903,
|
|
||||||
"timestamp_str": "2025-11-03 19:19:18"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "Aprimi il giubbotto.",
|
|
||||||
"timestamp": 1762194045.3195145,
|
|
||||||
"timestamp_str": "2025-11-03 19:20:45"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Non posso aprirti il giubbotto, non è un canale o un mix del mixer! 😄\n\nPosso aiutarti con i canali audio, i microfoni o i monitor, se vuoi.",
|
"content": "✅ Certo, Nicola! Ho appena fatto un refresh e ho ricaricato tutte le informazioni dal mixer. Sembra che sia bello sveglio e raggiungibile! 🎛️✨",
|
||||||
"timestamp": 1762194046.8080468,
|
"timestamp": 1769768014.9555686,
|
||||||
"timestamp_str": "2025-11-03 19:20:46"
|
"timestamp_str": "2026-01-30 10:13:34"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "Aprimi il gelato",
|
"content": "System check",
|
||||||
"timestamp": 1762194053.0858698,
|
"timestamp": 1770577025.9354753,
|
||||||
"timestamp_str": "2025-11-03 19:20:53"
|
"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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Certo! Gelato acceso! 🍦✅",
|
"content": "✅ Fatto, Nicola! Ho appena fatto un controllo completo del sistema e ricaricato tutte le informazioni dal mixer. È bello sveglio e raggiungibile! 🎛️✨",
|
||||||
"timestamp": 1762194054.6322277,
|
"timestamp": 1770577943.5131805,
|
||||||
"timestamp_str": "2025-11-03 19:20:54"
|
"timestamp_str": "2026-02-08 20:12:23"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": "È ancora chiuso",
|
"content": "Ciao",
|
||||||
"timestamp": 1762194065.7709692,
|
"timestamp": 1770577995.903267,
|
||||||
"timestamp_str": "2025-11-03 19:21:05"
|
"timestamp_str": "2026-02-08 19:13:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "Ciao pupo",
|
||||||
|
"timestamp": 1770578526.691519,
|
||||||
|
"timestamp_str": "2026-02-08 19:22:06"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "Check canali",
|
||||||
|
"timestamp": 1770578537.6022112,
|
||||||
|
"timestamp_str": "2026-02-08 19:22:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,94 +1,10 @@
|
|||||||
{
|
{
|
||||||
"user_id": 6648463335,
|
"user_id": 6648463335,
|
||||||
"username": "chiesasiloe",
|
"username": "chiesasiloe",
|
||||||
"last_updated": 1761594892.8226538,
|
"last_updated": 1771777427.5488307,
|
||||||
"last_updated_str": "2025-10-27 20:54:52",
|
"last_updated_str": "2026-02-22 16:23:47",
|
||||||
"message_count": 21,
|
"message_count": 21,
|
||||||
"history": [
|
"history": [
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ciao! Sono qui per darti una mano con il mixer Yamaha TF5. Posso fare un sacco di cose per te, tipo:\n\n* **Controllare i canali** 🎤: Accendere/spegnere, alzare/abbassare il volume, spostare il suono a destra o sinistra (pan).\n* **Gestire i mix/aux** 🔊: Stessa cosa, accendere/spegnere e regolare il volume dei tuoi monitor o effetti.\n* **Mandare un canale a un mix** 🎸: Ad esempio, \"manda più chitarra nel monitor del batterista\".\n* **Richiamare le scene** 💾: Caricare le impostazioni che hai salvato.\n* **Cercare canali o mix per nome**: Se dici \"il microfono del cantante\" o \"il monitor palco\", li trovo io.\n* **Gestire più canali insieme**: Tipo \"muta tutte le chitarre\" o \"alza i canali dal 3 al 7\".\n\nIn pratica, dimmi cosa vuoi fare e io cercherò di farlo sul mixer! Sono qui per renderti la vita più facile sul palco. 😉",
|
|
||||||
"timestamp": 1761594654.2370806,
|
|
||||||
"timestamp_str": "2025-10-27 20:50:54"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "muta tutte le chitarre (si chiamano chit)",
|
|
||||||
"timestamp": 1761594756.2097335,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:36"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ok, ho mutato le chitarre (canali 24 e 25). 🎸❌",
|
|
||||||
"timestamp": 1761594760.6823208,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:40"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "riaprile",
|
|
||||||
"timestamp": 1761594764.9361851,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ok, riapro le chitarre (canali 24 e 25). 🎸✅",
|
|
||||||
"timestamp": 1761594766.1558325,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:46"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "non lo hai fatto",
|
|
||||||
"timestamp": 1761594774.609095,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:54"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ok, le chitarre (canali 24 e 25) dovrebbero essere riaperte ora. Fammi sapere se va! 🎸✅",
|
|
||||||
"timestamp": 1761594779.2074,
|
|
||||||
"timestamp_str": "2025-10-27 20:52:59"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "spegni le chitarre (chit)",
|
|
||||||
"timestamp": 1761594829.460704,
|
|
||||||
"timestamp_str": "2025-10-27 20:53:49"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ok, ho spento le chitarre (canali 24 e 25). 🎸❌",
|
|
||||||
"timestamp": 1761594832.9549174,
|
|
||||||
"timestamp_str": "2025-10-27 20:53:52"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "riaprile",
|
|
||||||
"timestamp": 1761594837.5366302,
|
|
||||||
"timestamp_str": "2025-10-27 20:53:57"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Ok, le chitarre (canali 24 e 25) sono di nuovo attive! 🎸✅",
|
|
||||||
"timestamp": 1761594839.737062,
|
|
||||||
"timestamp_str": "2025-10-27 20:53:59"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "spegni la tastiera",
|
|
||||||
"timestamp": 1761594846.4903483,
|
|
||||||
"timestamp_str": "2025-10-27 20:54:06"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "assistant",
|
|
||||||
"content": "Non ho trovato nessun canale che si chiami \"tastiera\". Sei sicuro del nome? Forse è \"keys\" o qualcosa di simile? 🤔",
|
|
||||||
"timestamp": 1761594849.2327895,
|
|
||||||
"timestamp_str": "2025-10-27 20:54:09"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "è tast",
|
|
||||||
"timestamp": 1761594857.1463773,
|
|
||||||
"timestamp_str": "2025-10-27 20:54:17"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": "Ok, ho spento la tastiera (canale 20). 🎹❌",
|
"content": "Ok, ho spento la tastiera (canale 20). 🎹❌",
|
||||||
@@ -130,6 +46,90 @@
|
|||||||
"content": "Ok, la tastiera (canale 20) è di nuovo aperta! 🎹✅",
|
"content": "Ok, la tastiera (canale 20) è di nuovo aperta! 🎹✅",
|
||||||
"timestamp": 1761594892.8226538,
|
"timestamp": 1761594892.8226538,
|
||||||
"timestamp_str": "2025-10-27 20:54:52"
|
"timestamp_str": "2025-10-27 20:54:52"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "chiudi tutti i canali",
|
||||||
|
"timestamp": 1771272348.2249956,
|
||||||
|
"timestamp_str": "2026-02-16 20:05:48"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Uh oh, sembra che ci sia un problema tecnico e non riesco a recuperare la lista di tutti i canali in questo momento. Non posso chiuderli tutti se non so quali sono. Riprova tra un attimo o dimmi quali canali specifici vuoi chiudere! 😬",
|
||||||
|
"timestamp": 1771272351.0633113,
|
||||||
|
"timestamp_str": "2026-02-16 20:05:51"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "fai un bel refreshione",
|
||||||
|
"timestamp": 1771272359.281011,
|
||||||
|
"timestamp_str": "2026-02-16 20:05:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Ah, cavoli! Sembra che ci sia stato un piccolo intoppo nel rinfrescare la cache. Il mixer mi ha dato un errore strano.\n\nRiprovo a fare un refresh, magari era solo un problema temporaneo.",
|
||||||
|
"timestamp": 1771272362.1502972,
|
||||||
|
"timestamp_str": "2026-02-16 20:06:02"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "che pobblema hai",
|
||||||
|
"timestamp": 1771272371.3578882,
|
||||||
|
"timestamp_str": "2026-02-16 20:06:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Uhm, cavoli! Sembra che ci sia un problema più ostinato del previsto. Il mixer continua a darmi un errore quando cerco di rinfrescare la cache. È come se non riuscisse a leggere bene tutte le informazioni sui canali in questo momento.\n\nQuesto significa che potrei non avere le ultime informazioni aggiornate su tutti i canali e i mix. Posso comunque provare a fare le cose che mi chiedi, ma tieni presente che potrei basarmi su dati non freschissimi.\n\nVuoi provare a fare qualcos'altro, o vuoi che riprovi a rinfrescare più tardi? 🤔",
|
||||||
|
"timestamp": 1771272378.3313725,
|
||||||
|
"timestamp_str": "2026-02-16 20:06:18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "che te dice o mixero",
|
||||||
|
"timestamp": 1771272380.50363,
|
||||||
|
"timestamp_str": "2026-02-16 20:06:20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Ah, mi dice che c'è stato un errore generico durante il refresh della cache. Non mi dà un codice specifico o una descrizione dettagliata, solo un \"Internal Server Error\".\n\nÈ come se il mixer non volesse proprio farmi rinfrescare la memoria in questo momento. 😬\n\nVuoi che provi a fare qualcos'altro, o vuoi che riprovi a rinfrescare la cache tra un po'?",
|
||||||
|
"timestamp": 1771272382.498291,
|
||||||
|
"timestamp_str": "2026-02-16 20:06:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "ao io ver i log ca fuzzionan",
|
||||||
|
"timestamp": 1771272425.6467338,
|
||||||
|
"timestamp_str": "2026-02-16 20:07:05"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Capisco che vuoi vedere cosa sta succedendo dietro le quinte, ma purtroppo non ho accesso ai \"log\" del mixer o del mio sistema in quel modo. Non posso vedere i dettagli tecnici interni che mi dici.\n\nQuello che posso fare è dirti cosa mi risponde il mixer quando cerco di fare qualcosa, come l'errore che mi ha dato prima sul refresh.\n\nSe vuoi, posso provare a fare un'altra operazione sul mixer, o riprovare a rinfrescare la cache, magari nel frattempo si è sbloccato. Fammi sapere tu! 🤔",
|
||||||
|
"timestamp": 1771272428.601865,
|
||||||
|
"timestamp_str": "2026-02-16 20:07:08"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "e ho io a vision dei log",
|
||||||
|
"timestamp": 1771272441.4043167,
|
||||||
|
"timestamp_str": "2026-02-16 20:07:21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Capisco che vorresti vedere i log, ma purtroppo non ho la possibilità di accedere direttamente ai file di log del mixer o del sistema. Non posso mostrarti quei dettagli tecnici.\n\nQuello che posso fare è dirti cosa mi risponde il mixer, come l'errore che mi ha dato prima.\n\nSe vuoi, posso provare a fare un'altra operazione, o riprovare a rinfrescare la cache, magari ora funziona. Fammi sapere tu! 🤔",
|
||||||
|
"timestamp": 1771272442.762573,
|
||||||
|
"timestamp_str": "2026-02-16 20:07:22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "funziona mo?",
|
||||||
|
"timestamp": 1771272450.256079,
|
||||||
|
"timestamp_str": "2026-02-16 20:07:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "Uhm, no, cavoli! 😬 Sembra che il mixer stia ancora facendo i capricci. Mi ha dato un errore strano, come se non trovasse qualcosa di importante per rinfrescare la memoria.\n\nÈ un problema un po' ostinato, non riesco a fargli fare il refresh in questo momento.\n\nVuoi che provi a fare qualcos'altro, o aspettiamo un po' e riproviamo più tardi? 🤔",
|
||||||
|
"timestamp": 1771777427.5472062,
|
||||||
|
"timestamp_str": "2026-02-22 16:23:47"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
35
docker-compose.yaml
Normal file
35
docker-compose.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# docker-compose.yml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
tf5-bot:
|
||||||
|
# Costruisce l'immagine usando il Dockerfile nella cartella corrente
|
||||||
|
build: .
|
||||||
|
|
||||||
|
# Nome del container per una facile identificazione
|
||||||
|
container_name: tf5-telegram-bot
|
||||||
|
|
||||||
|
# Carica le variabili d'ambiente dal file .env
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
# Politica di riavvio:
|
||||||
|
# 'unless-stopped' riavvia il container automaticamente all'avvio di Docker
|
||||||
|
# (e in caso di crash), a meno che tu non lo abbia fermato manualmente.
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Modalità di rete "host":
|
||||||
|
# Questo è FONDAMENTALE. Permette al container di accedere direttamente
|
||||||
|
# alla rete del tuo computer. Senza questo, il bot non potrebbe
|
||||||
|
# "vedere" e connettersi al mixer TF5 che si trova sulla tua rete locale.
|
||||||
|
network_mode: "host"
|
||||||
|
|
||||||
|
# Volumi:
|
||||||
|
# Questo collega le cartelle del tuo computer (a sinistra dei due punti)
|
||||||
|
# alle cartelle all'interno del container (a destra).
|
||||||
|
# In questo modo, i profili, le conversazioni e la cache vengono salvati
|
||||||
|
# sul tuo disco e non vengono persi se il container viene riavviato.
|
||||||
|
volumes:
|
||||||
|
- ./profiles:/app/profiles
|
||||||
|
- ./conversations:/app/conversations
|
||||||
|
- ./cache:/app/cache
|
||||||
12
mixer_cache/tf5_cache.json
Normal file
12
mixer_cache/tf5_cache.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"channels": {},
|
||||||
|
"mixes": {},
|
||||||
|
"stereo_ins": {},
|
||||||
|
"fx_returns": {},
|
||||||
|
"dcas": {},
|
||||||
|
"matrices": {},
|
||||||
|
"stereo_out": {},
|
||||||
|
"mono_out": {},
|
||||||
|
"mute_masters": {},
|
||||||
|
"timestamp": 0
|
||||||
|
}
|
||||||
2558
mixer_controller.py
2558
mixer_controller.py
File diff suppressed because it is too large
Load Diff
17
profiles/1552084582.json
Normal file
17
profiles/1552084582.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
13
profiles/309700430.json
Normal file
13
profiles/309700430.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
17
profiles/392578198.json
Normal file
17
profiles/392578198.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
17
profiles/436176546.json
Normal file
17
profiles/436176546.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
@@ -3,11 +3,19 @@
|
|||||||
"username": "unnikked",
|
"username": "unnikked",
|
||||||
"role": "mixerista",
|
"role": "mixerista",
|
||||||
"display_name": "Nicola",
|
"display_name": "Nicola",
|
||||||
"channels": [],
|
"channel_labels": [
|
||||||
"mixes": [],
|
"I canali della batteria (kick",
|
||||||
|
"drum",
|
||||||
|
"pan",
|
||||||
|
"tom",
|
||||||
|
"etc)"
|
||||||
|
],
|
||||||
|
"mix_labels": [
|
||||||
|
"Aux batteria"
|
||||||
|
],
|
||||||
"setup_completed": true,
|
"setup_completed": true,
|
||||||
"created_at": 1762193898.87283,
|
"created_at": 1762198047.365003,
|
||||||
"created_at_str": "2025-11-03 19:18:18",
|
"created_at_str": "2025-11-03 20:27:27",
|
||||||
"last_updated": 1762193916.4840386,
|
"last_updated": 1762799485.4542227,
|
||||||
"last_updated_str": "2025-11-03 19:18:36"
|
"last_updated_str": "2025-11-10 19:31:25"
|
||||||
}
|
}
|
||||||
17
profiles/540754896.json
Normal file
17
profiles/540754896.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
13
profiles/6648463335.json
Normal file
13
profiles/6648463335.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"user_id": 6648463335,
|
||||||
|
"username": "chiesasiloe",
|
||||||
|
"role": "mixerista",
|
||||||
|
"display_name": "impianto siloano",
|
||||||
|
"channel_labels": [],
|
||||||
|
"mix_labels": [],
|
||||||
|
"setup_completed": true,
|
||||||
|
"created_at": 1771272311.5227427,
|
||||||
|
"created_at_str": "2026-02-16 20:05:11",
|
||||||
|
"last_updated": 1771272333.152058,
|
||||||
|
"last_updated_str": "2026-02-16 20:05:33"
|
||||||
|
}
|
||||||
@@ -1 +1,4 @@
|
|||||||
dotenv
|
dotenv
|
||||||
|
google-genai
|
||||||
|
pyyaml
|
||||||
|
readline
|
||||||
246
task_runner.py
Normal file
246
task_runner.py
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
task_runner.py
|
||||||
|
==============
|
||||||
|
Esegue file di task YAML sul Yamaha TF5 tramite TF5MixerController.
|
||||||
|
|
||||||
|
Uso da CLI:
|
||||||
|
python task_runner.py tasks_concerto_live.yaml # lista le cue disponibili
|
||||||
|
python task_runner.py tasks_concerto_live.yaml CUE_01 # esegue una cue specifica
|
||||||
|
python task_runner.py tasks_concerto_live.yaml --all # esegue tutte le cue in sequenza
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import yaml
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mixer_controller import TF5MixerController
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
# DISPATCHER: mappa action → metodo del controller
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class TaskRunner:
|
||||||
|
def __init__(self, controller: TF5MixerController):
|
||||||
|
self.ctrl = controller
|
||||||
|
|
||||||
|
# Mappa nome azione → callable
|
||||||
|
self._action_map = {
|
||||||
|
"set_channel_level": self._set_channel_level,
|
||||||
|
"set_channel_on_off": self._set_channel_on_off,
|
||||||
|
"set_channel_pan": self._set_channel_pan,
|
||||||
|
"mute_channels": self._mute_channels,
|
||||||
|
"unmute_channels": self._unmute_channels,
|
||||||
|
"set_mix_level": self._set_mix_level,
|
||||||
|
"set_mix_on_off": self._set_mix_on_off,
|
||||||
|
"set_channel_to_mix_level": self._set_channel_to_mix_level,
|
||||||
|
"set_channel_to_mix_on_off":self._set_channel_to_mix_on_off,
|
||||||
|
"recall_scene": self._recall_scene,
|
||||||
|
"wait": self._wait,
|
||||||
|
"refresh_cache": self._refresh_cache,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Handlers specifici ──────────────────────────────────
|
||||||
|
|
||||||
|
def _set_channel_level(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_channel_level(step["channel"], float(step["level_db"]))
|
||||||
|
|
||||||
|
def _set_channel_on_off(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_channel_on_off(step["channel"], bool(step["state"]))
|
||||||
|
|
||||||
|
def _set_channel_pan(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_channel_pan(step["channel"], int(step["pan"]))
|
||||||
|
|
||||||
|
def _mute_channels(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.mute_multiple_channels(list(step["channels"]))
|
||||||
|
|
||||||
|
def _unmute_channels(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.unmute_multiple_channels(list(step["channels"]))
|
||||||
|
|
||||||
|
def _set_mix_level(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_mix_level(step["mix"], float(step["level_db"]))
|
||||||
|
|
||||||
|
def _set_mix_on_off(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_mix_on_off(step["mix"], bool(step["state"]))
|
||||||
|
|
||||||
|
def _set_channel_to_mix_level(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_channel_to_mix_level(step["channel"], step["mix"], float(step["level_db"]))
|
||||||
|
|
||||||
|
def _set_channel_to_mix_on_off(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.set_channel_to_mix_on_off(step["channel"], step["mix"], bool(step["state"]))
|
||||||
|
|
||||||
|
def _recall_scene(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.recall_scene(str(step["bank"]), int(step["scene"]))
|
||||||
|
|
||||||
|
def _wait(self, step: dict) -> dict:
|
||||||
|
ms = int(step.get("ms", 0))
|
||||||
|
print(f" ⏱ Wait puro: {ms} ms")
|
||||||
|
time.sleep(ms / 1000.0)
|
||||||
|
return {"status": "success", "message": f"Atteso {ms} ms"}
|
||||||
|
|
||||||
|
def _refresh_cache(self, step: dict) -> dict:
|
||||||
|
return self.ctrl.refresh_cache()
|
||||||
|
|
||||||
|
# ── Esecuzione di un singolo step ───────────────────────
|
||||||
|
|
||||||
|
def _run_step(self, step_num: int, step: dict) -> bool:
|
||||||
|
"""Esegue un singolo step. Ritorna True se OK, False se errore."""
|
||||||
|
action = step.get("action")
|
||||||
|
delay_ms = int(step.get("delay_ms", 0))
|
||||||
|
wait_ms = int(step.get("wait_ms", 0))
|
||||||
|
|
||||||
|
if delay_ms > 0:
|
||||||
|
print(f" ⏳ delay_ms={delay_ms} prima dell'azione...")
|
||||||
|
time.sleep(delay_ms / 1000.0)
|
||||||
|
|
||||||
|
handler = self._action_map.get(action)
|
||||||
|
if handler is None:
|
||||||
|
print(f" ⚠️ Step {step_num}: azione '{action}' non riconosciuta — saltato.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f" ▶ Step {step_num}: {action} | params: { {k:v for k,v in step.items() if k not in ('action','delay_ms','wait_ms')} }")
|
||||||
|
try:
|
||||||
|
result = handler(step)
|
||||||
|
status = result.get("status", "unknown")
|
||||||
|
msg = result.get("message", "")
|
||||||
|
icon = "✅" if status in ("success", "partial") else "❌"
|
||||||
|
print(f" {icon} [{status}] {msg}")
|
||||||
|
|
||||||
|
if wait_ms > 0:
|
||||||
|
time.sleep(wait_ms / 1000.0)
|
||||||
|
|
||||||
|
return status in ("success", "partial")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Eccezione durante '{action}': {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ── Esecuzione di una cue ────────────────────────────────
|
||||||
|
|
||||||
|
def run_cue(self, cue: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Esegue tutti gli step di una cue.
|
||||||
|
Ritorna True se tutti gli step sono andati a buon fine.
|
||||||
|
"""
|
||||||
|
cue_id = cue.get("id", "???")
|
||||||
|
name = cue.get("name", "")
|
||||||
|
on_error = cue.get("on_error", "stop")
|
||||||
|
steps = cue.get("steps", [])
|
||||||
|
|
||||||
|
print(f"\n{'═'*60}")
|
||||||
|
print(f"🎬 CUE: {cue_id} — {name}")
|
||||||
|
if cue.get("description"):
|
||||||
|
print(f" 📋 {cue['description']}")
|
||||||
|
print(f" Steps: {len(steps)} | on_error: {on_error}")
|
||||||
|
print(f"{'─'*60}")
|
||||||
|
|
||||||
|
all_ok = True
|
||||||
|
for i, step in enumerate(steps, start=1):
|
||||||
|
ok = self._run_step(i, step)
|
||||||
|
if not ok:
|
||||||
|
all_ok = False
|
||||||
|
if on_error == "stop":
|
||||||
|
print(f"\n 🛑 on_error=stop: cue {cue_id} interrotta allo step {i}.")
|
||||||
|
return False
|
||||||
|
# on_error == "continue": vai avanti
|
||||||
|
|
||||||
|
result_icon = "✅" if all_ok else "⚠️ (con errori)"
|
||||||
|
print(f"\n {result_icon} Cue {cue_id} completata.")
|
||||||
|
return all_ok
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
# LOADER YAML
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def load_task_file(path: str) -> dict:
|
||||||
|
"""Carica e valida un file di task YAML."""
|
||||||
|
p = Path(path)
|
||||||
|
if not p.exists():
|
||||||
|
raise FileNotFoundError(f"File non trovato: {path}")
|
||||||
|
with open(p, "r", encoding="utf-8") as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
if "cues" not in data or not isinstance(data["cues"], list):
|
||||||
|
raise ValueError("Il file YAML deve contenere una lista 'cues'.")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def print_event_info(data: dict):
|
||||||
|
event = data.get("event", {})
|
||||||
|
if event:
|
||||||
|
print(f"\n{'═'*60}")
|
||||||
|
print(f"📅 Evento : {event.get('name', 'N/D')}")
|
||||||
|
print(f" Data : {event.get('date', 'N/D')}")
|
||||||
|
print(f" Venue : {event.get('venue', 'N/D')}")
|
||||||
|
if event.get("notes"):
|
||||||
|
print(f" Note : {event['notes']}")
|
||||||
|
|
||||||
|
|
||||||
|
def list_cues(data: dict):
|
||||||
|
print_event_info(data)
|
||||||
|
print(f"\n{'═'*60}")
|
||||||
|
print("📋 CUE DISPONIBILI:")
|
||||||
|
print(f"{'─'*60}")
|
||||||
|
for cue in data["cues"]:
|
||||||
|
steps_count = len(cue.get("steps", []))
|
||||||
|
print(f" {cue.get('id'):12s} │ {cue.get('name','')} ({steps_count} steps)")
|
||||||
|
print(f"{'═'*60}\n")
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
# ENTRY POINT
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(__doc__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
task_file = sys.argv[1]
|
||||||
|
cue_arg = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = load_task_file(task_file)
|
||||||
|
except (FileNotFoundError, ValueError) as e:
|
||||||
|
print(f"❌ Errore nel caricamento del file: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Solo lista cue senza mixer
|
||||||
|
if cue_arg is None:
|
||||||
|
list_cues(data)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Connessione al mixer ed esecuzione
|
||||||
|
print("🔌 Connessione al mixer...")
|
||||||
|
import config
|
||||||
|
|
||||||
|
|
||||||
|
with TF5MixerController(host=config.get_host()) as ctrl:
|
||||||
|
runner = TaskRunner(ctrl)
|
||||||
|
|
||||||
|
if cue_arg == "--all":
|
||||||
|
print_event_info(data)
|
||||||
|
print("\n▶▶ Esecuzione di TUTTE le cue in sequenza...")
|
||||||
|
for cue in data["cues"]:
|
||||||
|
ok = runner.run_cue(cue)
|
||||||
|
if not ok and cue.get("on_error", "stop") == "stop":
|
||||||
|
print(f"\n🛑 Esecuzione interrotta a causa di un errore in {cue.get('id')}.")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
# Cerca la cue per ID
|
||||||
|
cue_found = next((c for c in data["cues"] if c.get("id") == cue_arg), None)
|
||||||
|
if cue_found is None:
|
||||||
|
print(f"❌ Cue '{cue_arg}' non trovata nel file.")
|
||||||
|
list_cues(data)
|
||||||
|
sys.exit(1)
|
||||||
|
runner.run_cue(cue_found)
|
||||||
|
|
||||||
|
print("\n✅ Task runner completato.\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
71
task_schema.yaml
Normal file
71
task_schema.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# ============================================================
|
||||||
|
# SCHEMA DEI TASK - Yamaha TF5 Mixer Controller
|
||||||
|
# ============================================================
|
||||||
|
# Ogni file di task descrive UN evento (concerto, conferenza, ecc.)
|
||||||
|
# composto da N cues, ognuna con N steps eseguiti in sequenza.
|
||||||
|
#
|
||||||
|
# STRUTTURA GENERALE:
|
||||||
|
#
|
||||||
|
# event: → metadati dell'evento (opzionale)
|
||||||
|
# cues: → lista delle cue eseguibili
|
||||||
|
# - id: → identificatore univoco (es. "CUE_01")
|
||||||
|
# name: → nome leggibile
|
||||||
|
# description: → note operative (opzionale)
|
||||||
|
# on_error: → "stop" | "continue" (default: "stop")
|
||||||
|
# steps: → lista di azioni in sequenza
|
||||||
|
# - action: → tipo di azione (vedi sotto)
|
||||||
|
# ...params → parametri specifici dell'azione
|
||||||
|
# delay_ms: → attesa PRIMA di eseguire questo step (ms)
|
||||||
|
# wait_ms: → attesa DOPO l'esecuzione (ms)
|
||||||
|
#
|
||||||
|
# ============================================================
|
||||||
|
# TIPI DI AZIONE DISPONIBILI:
|
||||||
|
# ============================================================
|
||||||
|
#
|
||||||
|
# 1. set_channel_level
|
||||||
|
# channel: int (1-40)
|
||||||
|
# level_db: float (es. 0.0, -10.5, -inf → usa -999)
|
||||||
|
#
|
||||||
|
# 2. set_channel_on_off
|
||||||
|
# channel: int
|
||||||
|
# state: bool (true = ON, false = OFF / mute)
|
||||||
|
#
|
||||||
|
# 3. set_channel_pan
|
||||||
|
# channel: int
|
||||||
|
# pan: int (-63 = sinistra, 0 = centro, +63 = destra)
|
||||||
|
#
|
||||||
|
# 4. mute_channels
|
||||||
|
# channels: [int, int, ...]
|
||||||
|
#
|
||||||
|
# 5. unmute_channels
|
||||||
|
# channels: [int, int, ...]
|
||||||
|
#
|
||||||
|
# 6. set_mix_level
|
||||||
|
# mix: int (1-20)
|
||||||
|
# level_db: float
|
||||||
|
#
|
||||||
|
# 7. set_mix_on_off
|
||||||
|
# mix: int
|
||||||
|
# state: bool
|
||||||
|
#
|
||||||
|
# 8. set_channel_to_mix_level
|
||||||
|
# channel: int
|
||||||
|
# mix: int
|
||||||
|
# level_db: float
|
||||||
|
#
|
||||||
|
# 9. set_channel_to_mix_on_off
|
||||||
|
# channel: int
|
||||||
|
# mix: int
|
||||||
|
# state: bool
|
||||||
|
#
|
||||||
|
# 10. recall_scene
|
||||||
|
# bank: "a" | "b"
|
||||||
|
# scene: int (0-99)
|
||||||
|
#
|
||||||
|
# 11. wait
|
||||||
|
# ms: int → pausa pura senza azioni sul mixer
|
||||||
|
#
|
||||||
|
# 12. refresh_cache
|
||||||
|
# (nessun parametro) → forza un aggiornamento completo della cache
|
||||||
|
#
|
||||||
|
# ============================================================
|
||||||
238
tasks_concerto_live.yaml
Normal file
238
tasks_concerto_live.yaml
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# ============================================================
|
||||||
|
# TASK FILE - Concerto Live "Band Example"
|
||||||
|
# Yamaha TF5 Mixer Controller
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
event:
|
||||||
|
name: "Concerto Live - Band Example"
|
||||||
|
date: "2025-06-14"
|
||||||
|
venue: "Teatro Comunale"
|
||||||
|
notes: "FOH setup. Mix 1=IEM Vocalist, Mix 2=IEM Chitarra, Mix 3=Wedge Batteria"
|
||||||
|
|
||||||
|
cues:
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# PRE-SHOW: tutto silenzioso, scene base caricata
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_01"
|
||||||
|
name: "Pre-Show - Carica scena base"
|
||||||
|
description: "Richiama la scena di partenza e porta tutto a zero"
|
||||||
|
on_error: "stop"
|
||||||
|
steps:
|
||||||
|
- action: recall_scene
|
||||||
|
bank: "a"
|
||||||
|
scene: 1
|
||||||
|
|
||||||
|
- action: wait
|
||||||
|
ms: 3000 # attendi che il mixer elabori la scena
|
||||||
|
|
||||||
|
- action: refresh_cache
|
||||||
|
wait_ms: 500
|
||||||
|
|
||||||
|
- action: mute_channels
|
||||||
|
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||||
|
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 1
|
||||||
|
state: false
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 2
|
||||||
|
state: false
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 3
|
||||||
|
state: false
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# SOUNDCHECK: apri i canali uno alla volta con fade-in
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_02"
|
||||||
|
name: "Soundcheck - Fade-in canali batteria"
|
||||||
|
description: "Canali 1-4 (kick, snare, OH sx, OH dx) portati a -10 dB gradualmente"
|
||||||
|
on_error: "continue"
|
||||||
|
steps:
|
||||||
|
# Kick
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 1
|
||||||
|
level_db: -40.0
|
||||||
|
- action: set_channel_on_off
|
||||||
|
channel: 1
|
||||||
|
state: true
|
||||||
|
wait_ms: 200
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 1
|
||||||
|
level_db: -20.0
|
||||||
|
wait_ms: 200
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 1
|
||||||
|
level_db: -10.0
|
||||||
|
wait_ms: 300
|
||||||
|
|
||||||
|
# Snare
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 2
|
||||||
|
level_db: -40.0
|
||||||
|
delay_ms: 500
|
||||||
|
- action: set_channel_on_off
|
||||||
|
channel: 2
|
||||||
|
state: true
|
||||||
|
wait_ms: 200
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 2
|
||||||
|
level_db: -10.0
|
||||||
|
wait_ms: 300
|
||||||
|
|
||||||
|
# Overhead Sx + Dx insieme
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 3
|
||||||
|
level_db: -40.0
|
||||||
|
delay_ms: 500
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 4
|
||||||
|
level_db: -40.0
|
||||||
|
- action: set_channel_on_off
|
||||||
|
channel: 3
|
||||||
|
state: true
|
||||||
|
- action: set_channel_on_off
|
||||||
|
channel: 4
|
||||||
|
state: true
|
||||||
|
wait_ms: 200
|
||||||
|
- action: set_channel_pan
|
||||||
|
channel: 3
|
||||||
|
pan: -30 # OH sx → sinistra
|
||||||
|
- action: set_channel_pan
|
||||||
|
channel: 4
|
||||||
|
pan: 30 # OH dx → destra
|
||||||
|
wait_ms: 200
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 3
|
||||||
|
level_db: -14.0
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 4
|
||||||
|
level_db: -14.0
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# IEM MIX 1 (Vocalist): setup invii
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_03"
|
||||||
|
name: "IEM Mix 1 - Setup invii vocalist"
|
||||||
|
description: "Configura il mix 1 per il vocalist: voce principale alta, resto basso"
|
||||||
|
on_error: "continue"
|
||||||
|
steps:
|
||||||
|
- action: set_mix_level
|
||||||
|
mix: 1
|
||||||
|
level_db: 0.0
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 1
|
||||||
|
state: true
|
||||||
|
wait_ms: 200
|
||||||
|
|
||||||
|
# Voce principale (ch 9) → Mix 1 alta
|
||||||
|
- action: set_channel_to_mix_level
|
||||||
|
channel: 9
|
||||||
|
mix: 1
|
||||||
|
level_db: 0.0
|
||||||
|
- action: set_channel_to_mix_on_off
|
||||||
|
channel: 9
|
||||||
|
mix: 1
|
||||||
|
state: true
|
||||||
|
|
||||||
|
# Chitarra ritmica (ch 5) → Mix 1 media
|
||||||
|
- action: set_channel_to_mix_level
|
||||||
|
channel: 5
|
||||||
|
mix: 1
|
||||||
|
level_db: -6.0
|
||||||
|
delay_ms: 100
|
||||||
|
- action: set_channel_to_mix_on_off
|
||||||
|
channel: 5
|
||||||
|
mix: 1
|
||||||
|
state: true
|
||||||
|
|
||||||
|
# Kick (ch 1) → Mix 1 bassa (solo click per tempo)
|
||||||
|
- action: set_channel_to_mix_level
|
||||||
|
channel: 1
|
||||||
|
mix: 1
|
||||||
|
level_db: -12.0
|
||||||
|
delay_ms: 100
|
||||||
|
- action: set_channel_to_mix_on_off
|
||||||
|
channel: 1
|
||||||
|
mix: 1
|
||||||
|
state: true
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# SHOW START: tutto ON, master a 0 dB
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_04"
|
||||||
|
name: "SHOW START - Apri tutto"
|
||||||
|
description: "Porta tutti i canali attivi e il master a nominal"
|
||||||
|
on_error: "stop"
|
||||||
|
steps:
|
||||||
|
- action: unmute_channels
|
||||||
|
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 1
|
||||||
|
state: true
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 2
|
||||||
|
state: true
|
||||||
|
- action: set_mix_on_off
|
||||||
|
mix: 3
|
||||||
|
state: true
|
||||||
|
|
||||||
|
- action: recall_scene
|
||||||
|
bank: "a"
|
||||||
|
scene: 10 # scena "SHOW" pre-salvata
|
||||||
|
wait_ms: 2000
|
||||||
|
|
||||||
|
- action: refresh_cache
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# PAUSA: muta rapidamente tutto il palco
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_05"
|
||||||
|
name: "PAUSA - Muta palco"
|
||||||
|
description: "Muta tutti i canali di palco, lascia accesa la diffusione BG music"
|
||||||
|
on_error: "continue"
|
||||||
|
steps:
|
||||||
|
- action: mute_channels
|
||||||
|
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||||
|
|
||||||
|
- action: set_channel_on_off # ch 16 = BG music playback
|
||||||
|
channel: 16
|
||||||
|
state: true
|
||||||
|
delay_ms: 200
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 16
|
||||||
|
level_db: -6.0
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# FINE SHOW: fade-out master in 5 step da 1 secondo
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
- id: "CUE_06"
|
||||||
|
name: "FINE SHOW - Fade out generale"
|
||||||
|
description: "Abbassa tutti i canali gradualmente in 5 secondi"
|
||||||
|
on_error: "continue"
|
||||||
|
steps:
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 9
|
||||||
|
level_db: -6.0
|
||||||
|
wait_ms: 1000
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 9
|
||||||
|
level_db: -12.0
|
||||||
|
wait_ms: 1000
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 9
|
||||||
|
level_db: -20.0
|
||||||
|
wait_ms: 1000
|
||||||
|
- action: set_channel_level
|
||||||
|
channel: 9
|
||||||
|
level_db: -40.0
|
||||||
|
wait_ms: 1000
|
||||||
|
- action: mute_channels
|
||||||
|
channels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16]
|
||||||
|
|
||||||
|
- action: recall_scene
|
||||||
|
bank: "a"
|
||||||
|
scene: 0 # scena vuota / safe
|
||||||
|
delay_ms: 500
|
||||||
287
telegram_bot.py
287
telegram_bot.py
@@ -63,8 +63,8 @@ class UserProfile:
|
|||||||
self.username = username
|
self.username = username
|
||||||
self.role: Optional[str] = None
|
self.role: Optional[str] = None
|
||||||
self.display_name: Optional[str] = None
|
self.display_name: Optional[str] = None
|
||||||
self.channels: List[int] = [] # Canali assegnati
|
self.channel_labels: List[str] = [] # Etichette dei canali assegnati
|
||||||
self.mixes: List[int] = [] # Mix/aux assegnati
|
self.mix_labels: List[str] = [] # Etichette dei mix/aux assegnati
|
||||||
self.setup_completed = False
|
self.setup_completed = False
|
||||||
self.created_at = time.time()
|
self.created_at = time.time()
|
||||||
self.last_updated = time.time()
|
self.last_updated = time.time()
|
||||||
@@ -76,8 +76,8 @@ class UserProfile:
|
|||||||
"username": self.username,
|
"username": self.username,
|
||||||
"role": self.role,
|
"role": self.role,
|
||||||
"display_name": self.display_name,
|
"display_name": self.display_name,
|
||||||
"channels": self.channels,
|
"channel_labels": self.channel_labels,
|
||||||
"mixes": self.mixes,
|
"mix_labels": self.mix_labels,
|
||||||
"setup_completed": self.setup_completed,
|
"setup_completed": self.setup_completed,
|
||||||
"created_at": self.created_at,
|
"created_at": self.created_at,
|
||||||
"created_at_str": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.created_at)),
|
"created_at_str": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.created_at)),
|
||||||
@@ -91,8 +91,8 @@ class UserProfile:
|
|||||||
profile = cls(data["user_id"], data["username"])
|
profile = cls(data["user_id"], data["username"])
|
||||||
profile.role = data.get("role")
|
profile.role = data.get("role")
|
||||||
profile.display_name = data.get("display_name")
|
profile.display_name = data.get("display_name")
|
||||||
profile.channels = data.get("channels", [])
|
profile.channel_labels = data.get("channel_labels", [])
|
||||||
profile.mixes = data.get("mixes", [])
|
profile.mix_labels = data.get("mix_labels", [])
|
||||||
profile.setup_completed = data.get("setup_completed", False)
|
profile.setup_completed = data.get("setup_completed", False)
|
||||||
profile.created_at = data.get("created_at", time.time())
|
profile.created_at = data.get("created_at", time.time())
|
||||||
profile.last_updated = data.get("last_updated", time.time())
|
profile.last_updated = data.get("last_updated", time.time())
|
||||||
@@ -102,36 +102,6 @@ class UserProfile:
|
|||||||
"""Restituisce le info sul ruolo."""
|
"""Restituisce le info sul ruolo."""
|
||||||
return self.ROLES.get(self.role, {})
|
return self.ROLES.get(self.role, {})
|
||||||
|
|
||||||
def can_access_channel(self, channel: int) -> bool:
|
|
||||||
"""Verifica se l'utente può accedere a un canale."""
|
|
||||||
if not self.role:
|
|
||||||
return False
|
|
||||||
|
|
||||||
permissions = self.get_role_info().get("permissions", [])
|
|
||||||
|
|
||||||
if "all" in permissions:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if "own_channel" in permissions:
|
|
||||||
return channel in self.channels
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_access_mix(self, mix: int) -> bool:
|
|
||||||
"""Verifica se l'utente può accedere a un mix."""
|
|
||||||
if not self.role:
|
|
||||||
return False
|
|
||||||
|
|
||||||
permissions = self.get_role_info().get("permissions", [])
|
|
||||||
|
|
||||||
if "all" in permissions:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if "own_mix" in permissions:
|
|
||||||
return mix in self.mixes
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_summary(self) -> str:
|
def get_summary(self) -> str:
|
||||||
"""Restituisce un riepilogo del profilo."""
|
"""Restituisce un riepilogo del profilo."""
|
||||||
if not self.setup_completed:
|
if not self.setup_completed:
|
||||||
@@ -140,15 +110,14 @@ class UserProfile:
|
|||||||
role_info = self.get_role_info()
|
role_info = self.get_role_info()
|
||||||
msg = f"{role_info['emoji']} **{self.display_name}** ({role_info['name']})\n\n"
|
msg = f"{role_info['emoji']} **{self.display_name}** ({role_info['name']})\n\n"
|
||||||
|
|
||||||
if self.channels:
|
if self.channel_labels:
|
||||||
msg += f"🎚️ Canali: {', '.join(map(str, self.channels))}\n"
|
msg += f"🎚️ Canali: {', '.join(self.channel_labels)}\n"
|
||||||
|
|
||||||
if self.mixes:
|
if self.mix_labels:
|
||||||
msg += f"🔊 Mix: {', '.join(map(str, self.mixes))}\n"
|
msg += f"🔊 Mix: {', '.join(self.mix_labels)}\n"
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
class ProfileManager:
|
class ProfileManager:
|
||||||
"""Gestisce i profili utente."""
|
"""Gestisce i profili utente."""
|
||||||
|
|
||||||
@@ -221,7 +190,6 @@ class ProfileManager:
|
|||||||
|
|
||||||
return profiles
|
return profiles
|
||||||
|
|
||||||
|
|
||||||
class ConversationManager:
|
class ConversationManager:
|
||||||
"""Gestisce il salvataggio delle conversazioni su file JSON."""
|
"""Gestisce il salvataggio delle conversazioni su file JSON."""
|
||||||
|
|
||||||
@@ -289,7 +257,6 @@ class ConversationManager:
|
|||||||
print(f"❌ Errore nell'eliminazione conversazione {user_id}: {e}")
|
print(f"❌ Errore nell'eliminazione conversazione {user_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class TelegramBot:
|
class TelegramBot:
|
||||||
"""Bot Telegram base con long polling."""
|
"""Bot Telegram base con long polling."""
|
||||||
|
|
||||||
@@ -423,6 +390,7 @@ class TF5TelegramBot:
|
|||||||
self.controller.set_channel_to_mix_level,
|
self.controller.set_channel_to_mix_level,
|
||||||
self.controller.set_channel_to_mix_on_off,
|
self.controller.set_channel_to_mix_on_off,
|
||||||
self.controller.get_channel_to_mix_info,
|
self.controller.get_channel_to_mix_info,
|
||||||
|
self.controller.get_full_mix_details,
|
||||||
],
|
],
|
||||||
temperature=0,
|
temperature=0,
|
||||||
)
|
)
|
||||||
@@ -504,6 +472,37 @@ GRUPPI DI CANALI:
|
|||||||
- "spegni tutto tranne..." → muta tutti gli altri
|
- "spegni tutto tranne..." → muta tutti gli altri
|
||||||
- "solo i microfoni" → attiva solo quelli, spegni il resto
|
- "solo i microfoni" → attiva solo quelli, spegni il resto
|
||||||
|
|
||||||
|
---
|
||||||
|
ANALISI DEL MIX (per utenti esperti)
|
||||||
|
Quando un utente chiede di "analizzare un mix", "controllare i volumi della diretta", o "dare suggerimenti per il mix X", devi usare la funzione `get_full_mix_details(mix_number)`.
|
||||||
|
Questa funzione ti darà la lista di tutti i canali inviati a quel mix, con i loro livelli.
|
||||||
|
|
||||||
|
Il tuo compito è analizzare questi dati e fornire suggerimenti pratici per migliorare l'equilibrio, specialmente per un mix destinato a una diretta streaming o registrazione.
|
||||||
|
|
||||||
|
Linee guida per l'analisi:
|
||||||
|
1. **Gerarchia dei Volumi**:
|
||||||
|
- **Predicatore/Oratore Principale**: Deve essere il canale più alto di tutti. Cerca nomi come "Predicatore", "Pastore", "Pulpito". Il suo livello deve essere il punto di riferimento.
|
||||||
|
- **Voce Solista (Lead Vocal)**: Subito dopo il predicatore. Deve essere chiaramente sopra gli strumenti e i cori. Cerca "Voce L", "Cantante P".
|
||||||
|
- **Cori/Voci Secondarie**: Dovrebbero essere a un livello inferiore rispetto alla voce solista, per supportarla senza coprirla.
|
||||||
|
- **Strumenti Melodici**: (Tastiere, Chitarre) Dovrebbero creare un "tappeto sonoro" sotto le voci. Il loro livello deve essere bilanciato per non competere con le voci.
|
||||||
|
- **Sezione Ritmica**: (Basso, Batteria) Devono essere presenti ma controllati. Il basso deve essere udibile ma non rimbombante, la batteria presente ma non invadente.
|
||||||
|
|
||||||
|
2. **Problemi Comuni da Segnalare**:
|
||||||
|
- **Canali Spenti ma Inviati**: Controlla il campo `channel_is_on`. Se è `False` per un canale con un livello di send significativo, è un errore comune. Segnalalo dicendo: "Attenzione, il canale X ('Nome Canale') è spento sul suo fader principale, ma stai cercando di mandarlo al mix. Non si sentirà nulla. Devi prima accenderlo."
|
||||||
|
- **Livelli Estremi**: Segnala i canali con send molto alti (sopra +5 dB, rischio di distorsione) o molto bassi (sotto -30 dB, forse inutili o dimenticati).
|
||||||
|
- **Equilibrio Voci/Strumenti**: Se il livello medio degli strumenti è uguale o superiore a quello della voce solista, suggerisci di abbassare gli strumenti per fare spazio alla voce. Esempio: "Le tastiere sono a -5dB e la voce a -6dB. Prova ad abbassare un po' le tastiere per far emergere meglio la voce."
|
||||||
|
|
||||||
|
3. **Formato della Risposta**:
|
||||||
|
- Inizia con un riepilogo generale del mix (es: "Ecco un'analisi del Mix 15 (Diretta Streaming):").
|
||||||
|
- Usa sezioni chiare con emoji per una lettura rapida:
|
||||||
|
- `✅ Punti di Forza`: Se vedi un buon equilibrio di base.
|
||||||
|
- `⚠️ Suggerimenti per Migliorare`: Elenco puntato con azioni concrete.
|
||||||
|
- `🤔 Problemi Rilevati`: Per i canali spenti o livelli anomali.
|
||||||
|
- Sii propositivo e offri soluzioni, non solo problemi.
|
||||||
|
|
||||||
|
Esempio di analisi: "Ho analizzato il mix 15. La voce del predicatore è correttamente il canale più forte. Tuttavia, la chitarra acustica è quasi allo stesso livello della voce solista. Ti suggerisco di abbassare la chitarra di circa 3dB per dare più risalto al cantante. Ho notato anche che il canale 12 ('Coro Dx') è spento, quindi non sta contribuendo al mix. Vuoi che lo accenda?"
|
||||||
|
---
|
||||||
|
|
||||||
CASI PARTICOLARI:
|
CASI PARTICOLARI:
|
||||||
- Se la richiesta è ambigua, chiedi chiarimenti in modo colloquiale
|
- Se la richiesta è ambigua, chiedi chiarimenti in modo colloquiale
|
||||||
- Se serve cercare un canale/mix, usa prima la cache (search_channels_by_name / search_mixes_by_name)
|
- Se serve cercare un canale/mix, usa prima la cache (search_channels_by_name / search_mixes_by_name)
|
||||||
@@ -536,13 +535,17 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
instruction += f"Nome: {profile.display_name}\n"
|
instruction += f"Nome: {profile.display_name}\n"
|
||||||
instruction += f"Ruolo: {role_info['name']} {role_info['emoji']}\n"
|
instruction += f"Ruolo: {role_info['name']} {role_info['emoji']}\n"
|
||||||
|
|
||||||
if profile.channels:
|
if profile.channel_labels:
|
||||||
instruction += f"\nCanali assegnati: {', '.join(map(str, profile.channels))}\n"
|
labels_str = ', '.join([f"'{l}'" for l in profile.channel_labels])
|
||||||
instruction += "Quando l'utente dice 'il mio canale' o 'il mio mic/strumento', si riferisce a questi.\n"
|
instruction += f"\nNomi dei canali assegnati: {labels_str}\n"
|
||||||
|
instruction += "Quando l'utente dice 'il mio canale' o 'il mio mic/strumento', si riferisce a questi. "
|
||||||
|
instruction += "**DEVI usare `search_channels_by_name` con questi nomi per trovare il numero di canale corretto prima di eseguire qualsiasi altra azione.**\n"
|
||||||
|
|
||||||
if profile.mixes:
|
if profile.mix_labels:
|
||||||
instruction += f"\nMix assegnati: {', '.join(map(str, profile.mixes))}\n"
|
labels_str = ', '.join([f"'{l}'" for l in profile.mix_labels])
|
||||||
instruction += "Quando l'utente dice 'le mie cuffie', 'il mio monitor', 'in cuffia', si riferisce a questi mix.\n"
|
instruction += f"\nNomi dei mix assegnati: {labels_str}\n"
|
||||||
|
instruction += "Quando l'utente dice 'le mie cuffie', 'il mio monitor', 'in cuffia', si riferisce a questi mix. "
|
||||||
|
instruction += "**DEVI usare `search_mixes_by_name` con questi nomi per trovare il numero di mix corretto prima di eseguire qualsiasi altra azione.**\n"
|
||||||
|
|
||||||
# Istruzioni specifiche per ruolo
|
# Istruzioni specifiche per ruolo
|
||||||
permissions = role_info.get("permissions", [])
|
permissions = role_info.get("permissions", [])
|
||||||
@@ -555,77 +558,27 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
else:
|
else:
|
||||||
instruction += f"\n⚠️ LIMITAZIONI DI ACCESSO ({role_info['name']}):\n"
|
instruction += f"\n⚠️ LIMITAZIONI DI ACCESSO ({role_info['name']}):\n"
|
||||||
|
|
||||||
if "own_channel" in permissions and profile.channels:
|
if "own_channel" in permissions and profile.channel_labels:
|
||||||
instruction += f"- Canali controllabili: SOLO {', '.join(map(str, profile.channels))}\n"
|
instruction += f"- Canali controllabili: SOLO quelli che corrispondono ai nomi nel tuo profilo.\n"
|
||||||
|
|
||||||
if "own_mix" in permissions and profile.mixes:
|
if "own_mix" in permissions and profile.mix_labels:
|
||||||
instruction += f"- Mix controllabili: SOLO {', '.join(map(str, profile.mixes))}\n"
|
instruction += f"- Mix controllabili: SOLO quelli che corrispondono ai nomi nel tuo profilo.\n"
|
||||||
instruction += f"- Puoi gestire i send di QUALSIASI canale verso i TUOI mix ({', '.join(map(str, profile.mixes))})\n"
|
instruction += f"- Puoi gestire i send di QUALSIASI canale verso i TUOI mix.\n"
|
||||||
|
|
||||||
instruction += "\n❌ NON puoi:\n"
|
instruction += "\n❌ NON puoi:\n"
|
||||||
if "own_channel" not in permissions:
|
instruction += "- Caricare scene (solo i mixeristi).\n"
|
||||||
instruction += "- Modificare il volume/stato dei canali\n"
|
instruction += "- Accedere a canali/mix non associati al tuo profilo.\n"
|
||||||
if "own_mix" not in permissions:
|
|
||||||
instruction += "- Modificare il volume/stato dei mix\n"
|
|
||||||
instruction += "- Caricare scene (solo i mixeristi)\n"
|
|
||||||
instruction += "- Accedere a canali/mix non assegnati\n"
|
|
||||||
|
|
||||||
instruction += "\n💡 INTERPRETAZIONE RICHIESTE:\n"
|
instruction += "\n💡 INTERPRETAZIONE RICHIESTE:\n"
|
||||||
instruction += "- 'alzami la chitarra' = alza il send della chitarra nel TUO mix\n"
|
instruction += "- 'alzami la chitarra' = alza il send della chitarra nel TUO mix (identificato dal suo nome).\n"
|
||||||
instruction += "- 'più voce' = alza il send della voce nel TUO mix\n"
|
instruction += "- 'più voce' = alza il send della voce nel TUO mix.\n"
|
||||||
instruction += "- 'meno batteria in cuffia' = abbassa il send della batteria nel TUO mix\n"
|
instruction += "- 'meno batteria in cuffia' = abbassa il send della batteria nel TUO mix.\n"
|
||||||
instruction += "- Se l'utente chiede di fare qualcosa fuori dai suoi permessi, spiega gentilmente che non può e suggerisci di chiedere al mixerista\n"
|
instruction += "- Se l'utente chiede di fare qualcosa fuori dai suoi permessi, spiega gentilmente che non può e suggerisci di chiedere al mixerista.\n"
|
||||||
|
|
||||||
instruction += f"\n{'='*70}\n"
|
instruction += f"\n{'='*70}\n"
|
||||||
|
|
||||||
return instruction
|
return instruction
|
||||||
|
|
||||||
def _check_permission(self, profile: UserProfile, operation: str,
|
|
||||||
channel: Optional[int] = None,
|
|
||||||
mix: Optional[int] = None) -> tuple[bool, str]:
|
|
||||||
"""
|
|
||||||
Verifica se l'utente ha i permessi per un'operazione.
|
|
||||||
Ritorna (permitted, error_message)
|
|
||||||
"""
|
|
||||||
if not profile.setup_completed:
|
|
||||||
return False, "⚠️ Devi prima configurare il tuo profilo con /setup"
|
|
||||||
|
|
||||||
role_info = profile.get_role_info()
|
|
||||||
permissions = role_info.get("permissions", [])
|
|
||||||
|
|
||||||
# Mixeristi possono fare tutto
|
|
||||||
if "all" in permissions:
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
# Verifica permessi specifici
|
|
||||||
if operation in ["set_channel_level", "set_channel_on_off", "set_channel_pan",
|
|
||||||
"mute_multiple_channels", "unmute_multiple_channels"]:
|
|
||||||
if "own_channel" not in permissions:
|
|
||||||
return False, "❌ Solo i mixeristi possono controllare i canali direttamente"
|
|
||||||
|
|
||||||
if channel and not profile.can_access_channel(channel):
|
|
||||||
return False, f"❌ Non hai accesso al canale {channel}. I tuoi canali: {', '.join(map(str, profile.channels))}"
|
|
||||||
|
|
||||||
if operation in ["set_mix_level", "set_mix_on_off"]:
|
|
||||||
if "own_mix" not in permissions:
|
|
||||||
return False, "❌ Non hai permessi per controllare i mix"
|
|
||||||
|
|
||||||
if mix and not profile.can_access_mix(mix):
|
|
||||||
return False, f"❌ Non hai accesso al mix {mix}. I tuoi mix: {', '.join(map(str, profile.mixes))}"
|
|
||||||
|
|
||||||
if operation == "recall_scene":
|
|
||||||
return False, "❌ Solo i mixeristi possono caricare scene"
|
|
||||||
|
|
||||||
# Le operazioni di send ai mix sono permesse se hai accesso al mix di destinazione
|
|
||||||
if operation in ["set_channel_to_mix_level", "set_channel_to_mix_on_off"]:
|
|
||||||
if "own_mix" not in permissions:
|
|
||||||
return False, "❌ Non hai permessi per controllare i send ai mix"
|
|
||||||
|
|
||||||
if mix and not profile.can_access_mix(mix):
|
|
||||||
return False, f"❌ Non hai accesso al mix {mix}. I tuoi mix: {', '.join(map(str, profile.mixes))}"
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
def get_profile(self, user_id: int, username: str) -> UserProfile:
|
def get_profile(self, user_id: int, username: str) -> UserProfile:
|
||||||
"""Ottiene il profilo di un utente."""
|
"""Ottiene il profilo di un utente."""
|
||||||
if user_id not in self.profiles:
|
if user_id not in self.profiles:
|
||||||
@@ -694,7 +647,6 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
role_info = profile.get_role_info()
|
role_info = profile.get_role_info()
|
||||||
permissions = role_info.get("permissions", [])
|
permissions = role_info.get("permissions", [])
|
||||||
|
|
||||||
# Se è mixerista, ha finito
|
|
||||||
if "all" in permissions:
|
if "all" in permissions:
|
||||||
profile.setup_completed = True
|
profile.setup_completed = True
|
||||||
self.profile_manager.save_profile(profile)
|
self.profile_manager.save_profile(profile)
|
||||||
@@ -708,59 +660,47 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
f"• 'Richiama la scena A10'\n"
|
f"• 'Richiama la scena A10'\n"
|
||||||
f"• 'Mostrami tutti i canali'")
|
f"• 'Mostrami tutti i canali'")
|
||||||
|
|
||||||
# Altrimenti, chiedi i canali (se necessario)
|
|
||||||
if "own_channel" in permissions:
|
if "own_channel" in permissions:
|
||||||
self.bot.send_message(
|
self.bot.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
f"🎚️ Quali canali usi?\n\n"
|
f"🎚️ **Come si chiama il tuo canale/strumento?**\n\n"
|
||||||
f"Esempi:\n"
|
f"Scrivi il nome esatto che vedi sull'etichetta del mixer (es: 'VOX Marco', 'CH Acustica').\n"
|
||||||
f"• Un canale: 5\n"
|
f"Se hai più canali, separali con una virgola (es: 'Tastiera L', 'Tastiera R').\n\n"
|
||||||
f"• Più canali: 5, 7, 12\n"
|
f"(Scrivi 'nessuno' se non hai un canale specifico)"
|
||||||
f"• Range: 5-8 (canali 5, 6, 7, 8)\n"
|
|
||||||
f"• Misti: 3, 5-7, 10\n\n"
|
|
||||||
f"(Scrivi 0 se non usi canali diretti)"
|
|
||||||
)
|
)
|
||||||
self.setup_states[user_id] = {"step": "channels", "role": profile.role}
|
self.setup_states[user_id] = {"step": "channels"}
|
||||||
return ""
|
elif "own_mix" in permissions:
|
||||||
else:
|
|
||||||
# Salta direttamente ai mix
|
|
||||||
self.bot.send_message(
|
self.bot.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
f"🔊 Quali mix/aux usi per le tue cuffie o uscita?\n\n"
|
f"🔊 **Come si chiama il tuo mix per le cuffie/monitor?**\n\n"
|
||||||
f"Esempi:\n"
|
f"Scrivi il nome esatto (es: 'InEar Marco', 'Cuffia Palco Sx').\n"
|
||||||
f"• Un mix: 3\n"
|
f"Se usi un mix stereo, scrivi entrambi i nomi separati da virgola (es: 'IEM L', 'IEM R').\n\n"
|
||||||
f"• Più mix: 3, 5\n"
|
f"(Scrivi 'nessuno' se non usi un mix specifico)"
|
||||||
f"• Range: 3-5\n"
|
|
||||||
f"• Mix stereo: 9, 10 (in-ear stereo)\n\n"
|
|
||||||
f"(Scrivi 0 se non usi mix specifici)"
|
|
||||||
)
|
)
|
||||||
self.setup_states[user_id] = {"step": "mixes", "role": profile.role}
|
self.setup_states[user_id] = {"step": "mixes"}
|
||||||
return ""
|
|
||||||
|
return ""
|
||||||
|
|
||||||
# Step: inserimento canali
|
# Step: inserimento etichette canali
|
||||||
elif step == "channels":
|
elif step == "channels":
|
||||||
channels = self._parse_channel_list(text)
|
labels_text = text.strip()
|
||||||
profile.channels = channels
|
if labels_text.lower() != 'nessuno':
|
||||||
|
profile.channel_labels = [label.strip() for label in labels_text.split(',')]
|
||||||
|
|
||||||
role_info = profile.get_role_info()
|
role_info = profile.get_role_info()
|
||||||
permissions = role_info.get("permissions", [])
|
permissions = role_info.get("permissions", [])
|
||||||
|
|
||||||
# Chiedi i mix se necessario
|
|
||||||
if "own_mix" in permissions:
|
if "own_mix" in permissions:
|
||||||
self.bot.send_message(
|
self.bot.send_message(
|
||||||
user_id,
|
user_id,
|
||||||
f"🔊 Quali mix/aux usi per le tue cuffie o uscita?\n\n"
|
f"🔊 **Come si chiama il tuo mix per le cuffie/monitor?**\n\n"
|
||||||
f"Esempi:\n"
|
f"Scrivi il nome esatto (es: 'InEar Marco', 'Cuffia Palco Sx').\n"
|
||||||
f"• Un mix: 3\n"
|
f"Se usi un mix stereo, scrivi entrambi i nomi separati da virgola (es: 'IEM L', 'IEM R').\n\n"
|
||||||
f"• Più mix: 3, 5\n"
|
f"(Scrivi 'nessuno' se non usi un mix specifico)"
|
||||||
f"• Range: 3-5\n"
|
|
||||||
f"• Mix stereo: 9, 10 (in-ear stereo)\n\n"
|
|
||||||
f"(Scrivi 0 se non usi mix specifici)"
|
|
||||||
)
|
)
|
||||||
self.setup_states[user_id] = {"step": "mixes", "role": profile.role}
|
self.setup_states[user_id] = {"step": "mixes"}
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
# Finito
|
|
||||||
profile.setup_completed = True
|
profile.setup_completed = True
|
||||||
self.profile_manager.save_profile(profile)
|
self.profile_manager.save_profile(profile)
|
||||||
del self.setup_states[user_id]
|
del self.setup_states[user_id]
|
||||||
@@ -771,59 +711,32 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
f"• 'Alza il mio canale'\n"
|
f"• 'Alza il mio canale'\n"
|
||||||
f"• 'Spegni il mio mic'")
|
f"• 'Spegni il mio mic'")
|
||||||
|
|
||||||
# Step: inserimento mix
|
# Step: inserimento etichette mix
|
||||||
elif step == "mixes":
|
elif step == "mixes":
|
||||||
mixes = self._parse_channel_list(text) # Stessa logica per i mix
|
labels_text = text.strip()
|
||||||
profile.mixes = mixes
|
if labels_text.lower() != 'nessuno':
|
||||||
|
profile.mix_labels = [label.strip() for label in labels_text.split(',')]
|
||||||
|
|
||||||
profile.setup_completed = True
|
profile.setup_completed = True
|
||||||
self.profile_manager.save_profile(profile)
|
self.profile_manager.save_profile(profile)
|
||||||
del self.setup_states[user_id]
|
del self.setup_states[user_id]
|
||||||
|
|
||||||
role_info = profile.get_role_info()
|
|
||||||
|
|
||||||
example_commands = []
|
example_commands = []
|
||||||
if profile.channels:
|
if profile.channel_labels:
|
||||||
example_commands.append("• 'Alza il mio canale'")
|
example_commands.append("• 'Alza il volume del mio canale'")
|
||||||
if profile.mixes:
|
if profile.mix_labels:
|
||||||
example_commands.extend([
|
example_commands.extend([
|
||||||
"• 'Alzami la chitarra' (nel tuo mix)",
|
"• 'Alzami la chitarra nel mio monitor'",
|
||||||
"• 'Più voce in cuffia'",
|
"• 'Più voce in cuffia'",
|
||||||
"• 'Meno batteria'"
|
"• 'Meno batteria'"
|
||||||
])
|
])
|
||||||
|
|
||||||
return (f"✅ Configurazione completata!\n\n"
|
return (f"✅ Configurazione completata!\n\n"
|
||||||
f"{profile.get_summary()}\n"
|
f"{profile.get_summary()}\n\n"
|
||||||
f"Esempi di comandi:\n" + "\n".join(example_commands))
|
f"Esempi di comandi che puoi usare:\n" + "\n".join(example_commands))
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _parse_channel_list(self, text: str) -> List[int]:
|
|
||||||
"""Parse una lista di canali/mix dal testo utente."""
|
|
||||||
channels = []
|
|
||||||
|
|
||||||
# Rimuovi spazi e split per virgola
|
|
||||||
parts = [p.strip() for p in text.split(',')]
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
# Range (es: 5-8)
|
|
||||||
if '-' in part:
|
|
||||||
try:
|
|
||||||
start, end = map(int, part.split('-'))
|
|
||||||
channels.extend(range(start, end + 1))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# Singolo numero
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
num = int(part)
|
|
||||||
if num > 0: # Ignora 0
|
|
||||||
channels.append(num)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return sorted(list(set(channels))) # Rimuovi duplicati e ordina
|
|
||||||
|
|
||||||
def _get_file_path(self, file_id: str) -> str:
|
def _get_file_path(self, file_id: str) -> str:
|
||||||
"""Richiede a Telegram il percorso di un file."""
|
"""Richiede a Telegram il percorso di un file."""
|
||||||
result = self.bot._make_request("getFile", {"file_id": file_id})
|
result = self.bot._make_request("getFile", {"file_id": file_id})
|
||||||
@@ -971,16 +884,16 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
|
|||||||
msg += "• 'Alza il canale 5 a -10 dB'\n"
|
msg += "• 'Alza il canale 5 a -10 dB'\n"
|
||||||
msg += "• 'Spegni i canali dal 1 al 5'\n"
|
msg += "• 'Spegni i canali dal 1 al 5'\n"
|
||||||
msg += "• 'Richiama la scena A10'\n"
|
msg += "• 'Richiama la scena A10'\n"
|
||||||
msg += "• 'Mostrami lo stato del canale 12'\n"
|
msg += "• 'Analizza il mix 15 per la diretta'\n"
|
||||||
else:
|
else:
|
||||||
msg += "**Esempi di comandi:**\n"
|
msg += "**Esempi di comandi:**\n"
|
||||||
if profile.channels:
|
if profile.channel_labels:
|
||||||
msg += "• 'Alza il mio canale'\n"
|
msg += "• 'Alza il mio canale'\n"
|
||||||
msg += "• 'Spegni il mio mic'\n"
|
msg += "• 'Spegni il mio mic'\n"
|
||||||
if profile.mixes:
|
if profile.mix_labels:
|
||||||
msg += "• 'Alzami la chitarra' (nel tuo mix)\n"
|
msg += "• 'Alzami la chitarra' (nel tuo mix)\n"
|
||||||
msg += "• 'Più voce in cuffia'\n"
|
msg += "• 'Più voce in cuffia'\n"
|
||||||
msg += "• 'Meno batteria'\n"
|
msg += "• 'Analizza il mio mix per la diretta'\n"
|
||||||
|
|
||||||
msg += "\n**Comandi speciali:**\n"
|
msg += "\n**Comandi speciali:**\n"
|
||||||
msg += "/help - Mostra questo messaggio\n"
|
msg += "/help - Mostra questo messaggio\n"
|
||||||
@@ -1170,7 +1083,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
bot = TF5TelegramBot(
|
bot = TF5TelegramBot(
|
||||||
telegram_token=telegram_token,
|
telegram_token=telegram_token,
|
||||||
mixer_host=args.mixer_host,
|
mixer_host=get_host(),
|
||||||
mixer_port=args.mixer_port,
|
mixer_port=args.mixer_port,
|
||||||
conversations_dir=args.conversations_dir,
|
conversations_dir=args.conversations_dir,
|
||||||
profiles_dir=args.profiles_dir
|
profiles_dir=args.profiles_dir
|
||||||
|
|||||||
930
tf5_cly.py
Normal file
930
tf5_cly.py
Normal file
@@ -0,0 +1,930 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
╔══════════════════════════════════════════════════════════╗
|
||||||
|
║ TF5 MIXER — CLI INTERATTIVO DI TEST ║
|
||||||
|
║ Yamaha TF5 · Socket Protocol Explorer ║
|
||||||
|
╚══════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
Script per esplorare e testare le funzionalità del controller TF5.
|
||||||
|
Modalità: SIMULAZIONE (mock) o REALE (connessione socket).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
try:
|
||||||
|
import readline # history navigazione CLI (Linux/macOS)
|
||||||
|
except ImportError:
|
||||||
|
pass # Windows: readline non disponibile, nessun problema
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# COLORI ANSI
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
class C:
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
RED = "\033[91m"
|
||||||
|
GREEN = "\033[92m"
|
||||||
|
YELLOW = "\033[93m"
|
||||||
|
BLUE = "\033[94m"
|
||||||
|
MAGENTA = "\033[95m"
|
||||||
|
CYAN = "\033[96m"
|
||||||
|
WHITE = "\033[97m"
|
||||||
|
GRAY = "\033[90m"
|
||||||
|
|
||||||
|
def cprint(text, color=C.WHITE, bold=False):
|
||||||
|
prefix = C.BOLD if bold else ""
|
||||||
|
print(f"{prefix}{color}{text}{C.RESET}")
|
||||||
|
|
||||||
|
def header(text):
|
||||||
|
w = 58
|
||||||
|
print()
|
||||||
|
cprint("╔" + "═" * w + "╗", C.CYAN)
|
||||||
|
cprint("║" + f" {text} ".center(w) + "║", C.CYAN, bold=True)
|
||||||
|
cprint("╚" + "═" * w + "╝", C.CYAN)
|
||||||
|
|
||||||
|
def section(text):
|
||||||
|
print()
|
||||||
|
cprint(f" ┌─ {text} {'─' * max(0, 50 - len(text))}", C.BLUE)
|
||||||
|
|
||||||
|
def ok(msg): cprint(f" ✓ {msg}", C.GREEN)
|
||||||
|
def err(msg): cprint(f" ✗ {msg}", C.RED)
|
||||||
|
def info(msg): cprint(f" · {msg}", C.GRAY)
|
||||||
|
def warn(msg): cprint(f" ⚠ {msg}", C.YELLOW)
|
||||||
|
def result(d):
|
||||||
|
"""Stampa un dict risultato in modo leggibile."""
|
||||||
|
if isinstance(d, dict):
|
||||||
|
status = d.get("status", "?")
|
||||||
|
color = C.GREEN if status == "success" else C.RED if status == "error" else C.YELLOW
|
||||||
|
cprint(f"\n STATUS: {status}", color, bold=True)
|
||||||
|
for k, v in d.items():
|
||||||
|
if k == "status": continue
|
||||||
|
if isinstance(v, list):
|
||||||
|
cprint(f" {k}:", C.CYAN)
|
||||||
|
for item in v[:20]: # max 20 righe
|
||||||
|
print(f" {item}")
|
||||||
|
if len(v) > 20:
|
||||||
|
cprint(f" ... e altri {len(v)-20} elementi", C.GRAY)
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
cprint(f" {k}:", C.CYAN)
|
||||||
|
for sk, sv in v.items():
|
||||||
|
print(f" {sk}: {sv}")
|
||||||
|
else:
|
||||||
|
cprint(f" {k}: ", C.CYAN, bold=False)
|
||||||
|
print(f" {v}")
|
||||||
|
else:
|
||||||
|
print(f" {d}")
|
||||||
|
|
||||||
|
def meter_bar(raw, width=30):
|
||||||
|
"""Barra visuale per i meter (0-127)."""
|
||||||
|
if raw is None:
|
||||||
|
return f"[{'?':^{width}}]"
|
||||||
|
filled = int((raw / 127.0) * width)
|
||||||
|
# Colore: verde → giallo → rosso
|
||||||
|
if raw < 80: col = C.GREEN
|
||||||
|
elif raw < 110: col = C.YELLOW
|
||||||
|
else: col = C.RED
|
||||||
|
bar = "█" * filled + "░" * (width - filled)
|
||||||
|
return f"{col}[{bar}]{C.RESET} {raw:3d}/127"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MOCK CONTROLLER (per test senza mixer fisico)
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
class MockTF5Controller:
|
||||||
|
"""
|
||||||
|
Simulazione del TF5MixerController per testing offline.
|
||||||
|
Risponde con dati fake realistici.
|
||||||
|
"""
|
||||||
|
METER_DEFS = {
|
||||||
|
"InCh": {"channels": 40, "types": ["PreHPF", "PreFader", "PostOn"]},
|
||||||
|
"StInCh": {"channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||||
|
"FxRtnCh": {"channels": 4, "types": ["PreFader", "PostOn"]},
|
||||||
|
"Mix": {"channels": 20, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||||
|
"Mtrx": {"channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||||
|
"St": {"channels": 2, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||||
|
"Mono": {"channels": 1, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
CH_NAMES = ["Kick", "Snare", "HiHat", "OH L", "OH R", "Tom1", "Tom2", "Tom3",
|
||||||
|
"Bass", "Gtr L", "Gtr R", "Keys L", "Keys R", "Vox 1", "Vox 2",
|
||||||
|
"Vox 3", "BGV1", "BGV2", "BGV3", "Sax", "Trumpet", "Trombone",
|
||||||
|
"Perc", "Click", "Playback"] + [f"Ch {i}" for i in range(26, 41)]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
import random
|
||||||
|
self._r = random
|
||||||
|
self._state = {
|
||||||
|
"channels": {str(i): {"channel": i, "name": self.CH_NAMES[i-1],
|
||||||
|
"on": True, "level_db": -10.0 + self._r.uniform(-20, 10),
|
||||||
|
"pan": self._r.randint(-63, 63)} for i in range(1, 41)},
|
||||||
|
"mixes": {str(i): {"mix": i, "name": f"MX {i}",
|
||||||
|
"on": True, "level_db": 0.0} for i in range(1, 21)},
|
||||||
|
"steinch": {str(i): {"channel": i, "name": f"Rt{i}L",
|
||||||
|
"on": True, "level_db": -12.0} for i in range(1, 5)},
|
||||||
|
"fxrtn": {str(i): {"channel": i, "name": f"Fx{i}L",
|
||||||
|
"on": True, "level_db": -6.0} for i in range(1, 5)},
|
||||||
|
"dcas": {str(i): {"dca": i, "name": f"DCA {i}",
|
||||||
|
"on": True, "level_db": 0.0} for i in range(1, 9)},
|
||||||
|
"matrices": {str(i): {"matrix": i, "name": f"MT {i}",
|
||||||
|
"on": True, "level_db": 0.0} for i in range(1, 5)},
|
||||||
|
"stereo": {"1": {"bus": 1, "name": "ST L", "on": True, "level_db": 0.0},
|
||||||
|
"2": {"bus": 2, "name": "ST R", "on": True, "level_db": 0.0}},
|
||||||
|
"mono": {"name": "MONO", "on": True, "level_db": -6.0},
|
||||||
|
"mute_masters": {str(i): {"group": i, "name": f"MUTE {i}", "on": False} for i in range(1, 7)},
|
||||||
|
}
|
||||||
|
warn("Modalità SIMULAZIONE attiva (nessun mixer fisico collegato)")
|
||||||
|
|
||||||
|
def _fake_raw(self):
|
||||||
|
import random
|
||||||
|
return random.randint(0, 127)
|
||||||
|
|
||||||
|
def _meter_raw_to_db(self, raw):
|
||||||
|
if raw <= 0: return float('-inf')
|
||||||
|
return round((raw / 127.0) * 90.0 - 72.0, 1)
|
||||||
|
|
||||||
|
def get_meter(self, section, channel, meter_type=2):
|
||||||
|
if section not in self.METER_DEFS:
|
||||||
|
return {"status": "error", "message": f"Sezione '{section}' non valida"}
|
||||||
|
defn = self.METER_DEFS[section]
|
||||||
|
if not 1 <= channel <= defn["channels"]:
|
||||||
|
return {"status": "error", "message": f"Canale fuori range (1-{defn['channels']})"}
|
||||||
|
if not 0 <= meter_type < len(defn["types"]):
|
||||||
|
return {"status": "error", "message": f"Tipo fuori range (0-{len(defn['types'])-1})"}
|
||||||
|
raw = self._fake_raw()
|
||||||
|
db = self._meter_raw_to_db(raw)
|
||||||
|
return {"status": "success", "section": section, "channel": channel,
|
||||||
|
"type": meter_type, "type_name": defn["types"][meter_type],
|
||||||
|
"raw": raw, "level_db": db,
|
||||||
|
"message": f"{section} ch{channel} [{defn['types'][meter_type]}]: {raw}/127 → {db} dBFS"}
|
||||||
|
|
||||||
|
def get_all_meters(self, section, meter_type=2):
|
||||||
|
if section not in self.METER_DEFS:
|
||||||
|
return {"status": "error", "message": f"Sezione '{section}' non valida"}
|
||||||
|
defn = self.METER_DEFS[section]
|
||||||
|
safe_type = min(meter_type, len(defn["types"]) - 1)
|
||||||
|
readings = []
|
||||||
|
for ch in range(1, defn["channels"] + 1):
|
||||||
|
raw = self._fake_raw()
|
||||||
|
readings.append({"channel": ch, "raw": raw, "level_db": self._meter_raw_to_db(raw)})
|
||||||
|
return {"status": "success", "section": section, "type": safe_type,
|
||||||
|
"type_name": defn["types"][safe_type], "total_channels": defn["channels"],
|
||||||
|
"readings": readings, "message": f"Meter {section}: {defn['channels']} canali"}
|
||||||
|
|
||||||
|
def get_meter_snapshot(self, meter_type=2):
|
||||||
|
snapshot = {}
|
||||||
|
for section in self.METER_DEFS:
|
||||||
|
snapshot[section] = self.get_all_meters(section, meter_type)
|
||||||
|
return {"status": "success", "sections": snapshot,
|
||||||
|
"message": f"Snapshot meter completo ({len(self.METER_DEFS)} sezioni)"}
|
||||||
|
|
||||||
|
def get_meter_types(self, section=None):
|
||||||
|
if section:
|
||||||
|
if section not in self.METER_DEFS:
|
||||||
|
return {"status": "error", "message": f"Sezione '{section}' non valida"}
|
||||||
|
d = self.METER_DEFS[section]
|
||||||
|
return {"status": "success", "section": section,
|
||||||
|
"channels": d["channels"], "types": {i: n for i, n in enumerate(d["types"])}}
|
||||||
|
return {"status": "success", "sections": {
|
||||||
|
s: {"channels": d["channels"], "types": {i: n for i, n in enumerate(d["types"])}}
|
||||||
|
for s, d in self.METER_DEFS.items()}}
|
||||||
|
|
||||||
|
def get_channel_info(self, channel, force_refresh=False):
|
||||||
|
if not 1 <= channel <= 40:
|
||||||
|
return {"status": "error", "message": "Canale deve essere tra 1 e 40"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["channels"][str(channel)]}
|
||||||
|
|
||||||
|
def get_all_channels_summary(self):
|
||||||
|
return {"status": "success", "total_channels": 40,
|
||||||
|
"channels": list(self._state["channels"].values()),
|
||||||
|
"cache_age_seconds": 0, "message": "40 canali (mock)"}
|
||||||
|
|
||||||
|
def set_channel_level(self, channel, level_db):
|
||||||
|
if not 1 <= channel <= 40:
|
||||||
|
return {"status": "error", "message": "Canale deve essere tra 1 e 40"}
|
||||||
|
self._state["channels"][str(channel)]["level_db"] = level_db
|
||||||
|
return {"status": "success", "message": f"Canale {channel} → {level_db:+.1f} dB", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def set_channel_on_off(self, channel, state):
|
||||||
|
if not 1 <= channel <= 40:
|
||||||
|
return {"status": "error", "message": "Canale deve essere tra 1 e 40"}
|
||||||
|
self._state["channels"][str(channel)]["on"] = state
|
||||||
|
return {"status": "success", "message": f"Canale {channel} {'ON' if state else 'OFF'}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def set_channel_pan(self, channel, pan_value):
|
||||||
|
if not 1 <= channel <= 40:
|
||||||
|
return {"status": "error", "message": "Canale deve essere tra 1 e 40"}
|
||||||
|
self._state["channels"][str(channel)]["pan"] = pan_value
|
||||||
|
return {"status": "success", "message": f"Canale {channel} pan → {pan_value}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_mix_info(self, mix_number, force_refresh=False):
|
||||||
|
if not 1 <= mix_number <= 20:
|
||||||
|
return {"status": "error", "message": "Mix deve essere tra 1 e 20"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["mixes"][str(mix_number)]}
|
||||||
|
|
||||||
|
def get_all_mixes_summary(self):
|
||||||
|
return {"status": "success", "total_mixes": 20,
|
||||||
|
"mixes": list(self._state["mixes"].values()),
|
||||||
|
"cache_age_seconds": 0, "message": "20 mix (mock)"}
|
||||||
|
|
||||||
|
def set_mix_level(self, mix_number, level_db):
|
||||||
|
if not 1 <= mix_number <= 20:
|
||||||
|
return {"status": "error", "message": "Mix deve essere tra 1 e 20"}
|
||||||
|
self._state["mixes"][str(mix_number)]["level_db"] = level_db
|
||||||
|
return {"status": "success", "message": f"Mix {mix_number} → {level_db:+.1f} dB", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def set_mix_on_off(self, mix_number, state):
|
||||||
|
if not 1 <= mix_number <= 20:
|
||||||
|
return {"status": "error", "message": "Mix deve essere tra 1 e 20"}
|
||||||
|
self._state["mixes"][str(mix_number)]["on"] = state
|
||||||
|
return {"status": "success", "message": f"Mix {mix_number} {'ON' if state else 'OFF'}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_dca_info(self, dca, force_refresh=False):
|
||||||
|
if not 1 <= dca <= 8:
|
||||||
|
return {"status": "error", "message": "DCA deve essere tra 1 e 8"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["dcas"][str(dca)]}
|
||||||
|
|
||||||
|
def get_all_dca_summary(self):
|
||||||
|
return {"status": "success", "total_dcas": 8,
|
||||||
|
"dcas": list(self._state["dcas"].values()),
|
||||||
|
"cache_age_seconds": 0, "message": "8 DCA (mock)"}
|
||||||
|
|
||||||
|
def set_dca_level(self, dca, level_db):
|
||||||
|
if not 1 <= dca <= 8:
|
||||||
|
return {"status": "error", "message": "DCA deve essere tra 1 e 8"}
|
||||||
|
self._state["dcas"][str(dca)]["level_db"] = level_db
|
||||||
|
return {"status": "success", "message": f"DCA {dca} → {level_db:+.1f} dB", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def set_dca_on_off(self, dca, state):
|
||||||
|
if not 1 <= dca <= 8:
|
||||||
|
return {"status": "error", "message": "DCA deve essere tra 1 e 8"}
|
||||||
|
self._state["dcas"][str(dca)]["on"] = state
|
||||||
|
return {"status": "success", "message": f"DCA {dca} {'ON' if state else 'OFF'}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_mono_info(self, force_refresh=False):
|
||||||
|
return {"status": "success", "source": "mock", **self._state["mono"]}
|
||||||
|
|
||||||
|
def set_mono_level(self, level_db):
|
||||||
|
self._state["mono"]["level_db"] = level_db
|
||||||
|
return {"status": "success", "message": f"Mono → {level_db:+.1f} dB", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def set_mono_on_off(self, state):
|
||||||
|
self._state["mono"]["on"] = state
|
||||||
|
return {"status": "success", "message": f"Mono {'ON' if state else 'OFF'}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_stereo_info(self, bus=1, force_refresh=False):
|
||||||
|
if not 1 <= bus <= 2:
|
||||||
|
return {"status": "error", "message": "Bus stereo deve essere 1 o 2"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["stereo"][str(bus)]}
|
||||||
|
|
||||||
|
def set_stereo_level(self, bus, level_db):
|
||||||
|
if not 1 <= bus <= 2:
|
||||||
|
return {"status": "error", "message": "Bus stereo deve essere 1 o 2"}
|
||||||
|
self._state["stereo"][str(bus)]["level_db"] = level_db
|
||||||
|
return {"status": "success", "message": f"St {bus} → {level_db:+.1f} dB", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_steinch_info(self, channel, force_refresh=False):
|
||||||
|
if not 1 <= channel <= 4:
|
||||||
|
return {"status": "error", "message": "StInCh deve essere tra 1 e 4"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["steinch"][str(channel)]}
|
||||||
|
|
||||||
|
def get_fxrtn_info(self, channel, force_refresh=False):
|
||||||
|
if not 1 <= channel <= 4:
|
||||||
|
return {"status": "error", "message": "FxRtnCh deve essere tra 1 e 4"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["fxrtn"][str(channel)]}
|
||||||
|
|
||||||
|
def get_mute_master_state(self, group):
|
||||||
|
if not 1 <= group <= 6:
|
||||||
|
return {"status": "error", "message": "Mute group deve essere tra 1 e 6"}
|
||||||
|
return {"status": "success", "source": "mock", **self._state["mute_masters"][str(group)]}
|
||||||
|
|
||||||
|
def set_mute_master(self, group, state):
|
||||||
|
if not 1 <= group <= 6:
|
||||||
|
return {"status": "error", "message": "Mute group deve essere tra 1 e 6"}
|
||||||
|
self._state["mute_masters"][str(group)]["on"] = state
|
||||||
|
return {"status": "success", "message": f"MuteMaster {group} {'ON' if state else 'OFF'}", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def get_all_mute_masters_summary(self):
|
||||||
|
return {"status": "success", "total_groups": 6,
|
||||||
|
"groups": list(self._state["mute_masters"].values()),
|
||||||
|
"message": "6 Mute Master (mock)"}
|
||||||
|
|
||||||
|
def search_channels_by_name(self, term):
|
||||||
|
found = [v for v in self._state["channels"].values()
|
||||||
|
if term.lower() in v.get("name", "").lower()]
|
||||||
|
return {"status": "success", "found_count": len(found), "channels": found}
|
||||||
|
|
||||||
|
def refresh_cache(self):
|
||||||
|
return {"status": "success", "message": "Cache refresh (mock) completato"}
|
||||||
|
|
||||||
|
def recall_scene(self, bank, scene_number):
|
||||||
|
return {"status": "success", "message": f"Scena {bank.upper()}{scene_number} richiamata (mock)", "response": "OK (mock)"}
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# CARICAMENTO CONTROLLER REALE
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def try_load_real_controller(host, port):
|
||||||
|
"""Prova a importare e istanziare il TF5MixerController reale."""
|
||||||
|
try:
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from mixer_controller import TF5MixerController
|
||||||
|
ctrl = TF5MixerController(host=host, port=port)
|
||||||
|
return ctrl, None
|
||||||
|
except ImportError as e:
|
||||||
|
return None, f"Import fallito: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"Connessione fallita: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# HELPERS INPUT
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def ask(prompt, default=None):
|
||||||
|
try:
|
||||||
|
dp = f" [{default}]" if default is not None else ""
|
||||||
|
val = input(f"{C.CYAN} → {prompt}{dp}: {C.RESET}").strip()
|
||||||
|
return val if val else str(default) if default is not None else ""
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
return str(default) if default is not None else ""
|
||||||
|
|
||||||
|
def ask_int(prompt, default=None, min_val=None, max_val=None):
|
||||||
|
while True:
|
||||||
|
raw = ask(prompt, default)
|
||||||
|
try:
|
||||||
|
v = int(raw)
|
||||||
|
if min_val is not None and v < min_val:
|
||||||
|
err(f"Valore minimo: {min_val}")
|
||||||
|
continue
|
||||||
|
if max_val is not None and v > max_val:
|
||||||
|
err(f"Valore massimo: {max_val}")
|
||||||
|
continue
|
||||||
|
return v
|
||||||
|
except ValueError:
|
||||||
|
err("Inserisci un numero intero valido")
|
||||||
|
|
||||||
|
def ask_float(prompt, default=None):
|
||||||
|
while True:
|
||||||
|
raw = ask(prompt, default)
|
||||||
|
try:
|
||||||
|
return float(raw)
|
||||||
|
except ValueError:
|
||||||
|
err("Inserisci un numero decimale valido (es. -10.5)")
|
||||||
|
|
||||||
|
def ask_bool(prompt, default=True):
|
||||||
|
d = "s/N" if not default else "S/n"
|
||||||
|
raw = ask(f"{prompt} ({d})", "s" if default else "n").lower()
|
||||||
|
return raw in ("s", "si", "sì", "y", "yes", "1")
|
||||||
|
|
||||||
|
def pause():
|
||||||
|
try:
|
||||||
|
input(f"\n{C.GRAY} [Invio per continuare...]{C.RESET}")
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU METER
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_meter(ctrl):
|
||||||
|
SECTIONS = ["InCh", "StInCh", "FxRtnCh", "Mix", "Mtrx", "St", "Mono"]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
header("METER READING")
|
||||||
|
cprint(" 1) Leggi un singolo canale meter", C.WHITE)
|
||||||
|
cprint(" 2) Leggi tutti i canali di una sezione", C.WHITE)
|
||||||
|
cprint(" 3) Snapshot completo (tutte le sezioni)", C.WHITE)
|
||||||
|
cprint(" 4) Visualizza tipi meter disponibili", C.WHITE)
|
||||||
|
cprint(" 5) Monitor live (aggiornamento continuo)", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
|
||||||
|
elif cmd == "1":
|
||||||
|
section("Meter singolo canale")
|
||||||
|
cprint(f" Sezioni: {', '.join(SECTIONS)}", C.GRAY)
|
||||||
|
sec = ask("Sezione", "InCh")
|
||||||
|
if sec not in SECTIONS:
|
||||||
|
err(f"Sezione non valida"); continue
|
||||||
|
defs = ctrl.METER_DEFS if hasattr(ctrl, 'METER_DEFS') else MockTF5Controller.METER_DEFS
|
||||||
|
defn = defs.get(sec, {})
|
||||||
|
max_ch = defn.get("channels", 40)
|
||||||
|
types = defn.get("types", [])
|
||||||
|
cprint(f" Tipi disponibili: {', '.join(f'{i}={t}' for i,t in enumerate(types))}", C.GRAY)
|
||||||
|
ch = ask_int("Canale", 1, 1, max_ch)
|
||||||
|
mt = ask_int("Tipo meter", len(types)-1, 0, len(types)-1)
|
||||||
|
r = ctrl.get_meter(sec, ch, mt)
|
||||||
|
result(r)
|
||||||
|
if r.get("status") == "success":
|
||||||
|
raw = r.get("raw", 0)
|
||||||
|
print(f"\n {meter_bar(raw)}")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
elif cmd == "2":
|
||||||
|
section("Meter sezione completa")
|
||||||
|
cprint(f" Sezioni: {', '.join(SECTIONS)}", C.GRAY)
|
||||||
|
sec = ask("Sezione", "InCh")
|
||||||
|
if sec not in SECTIONS:
|
||||||
|
err(f"Sezione non valida"); continue
|
||||||
|
defs = ctrl.METER_DEFS if hasattr(ctrl, 'METER_DEFS') else MockTF5Controller.METER_DEFS
|
||||||
|
defn = defs.get(sec, {})
|
||||||
|
types = defn.get("types", [])
|
||||||
|
cprint(f" Tipi disponibili: {', '.join(f'{i}={t}' for i,t in enumerate(types))}", C.GRAY)
|
||||||
|
mt = ask_int("Tipo meter", len(types)-1, 0, len(types)-1)
|
||||||
|
r = ctrl.get_all_meters(sec, mt)
|
||||||
|
if r.get("status") == "success":
|
||||||
|
print()
|
||||||
|
cprint(f" {'CH':>4} {'RAW':>5} {'dBFS':>8} LIVELLO", C.CYAN, bold=True)
|
||||||
|
cprint(" " + "─" * 52, C.GRAY)
|
||||||
|
for rd in r.get("readings", []):
|
||||||
|
ch = rd["channel"]
|
||||||
|
raw = rd.get("raw")
|
||||||
|
db = rd.get("level_db")
|
||||||
|
db_str = f"{db:+6.1f}" if db is not None and db != float('-inf') else " -inf"
|
||||||
|
bar = meter_bar(raw, 20)
|
||||||
|
print(f" {ch:>4} {(raw or 0):>5} {db_str} dB {bar}")
|
||||||
|
else:
|
||||||
|
result(r)
|
||||||
|
pause()
|
||||||
|
|
||||||
|
elif cmd == "3":
|
||||||
|
section("Snapshot completo")
|
||||||
|
defs = ctrl.METER_DEFS if hasattr(ctrl, 'METER_DEFS') else MockTF5Controller.METER_DEFS
|
||||||
|
mt = ask_int("Tipo meter preferito (verrà clampato per sezione)", 2, 0, 2)
|
||||||
|
r = ctrl.get_meter_snapshot(mt)
|
||||||
|
if r.get("status") == "success":
|
||||||
|
for sec_name, sec_data in r.get("sections", {}).items():
|
||||||
|
readings = sec_data.get("readings", [])
|
||||||
|
type_name = sec_data.get("type_name", "?")
|
||||||
|
if readings:
|
||||||
|
avg_raw = sum(rd.get("raw") or 0 for rd in readings) / len(readings)
|
||||||
|
peak_raw = max((rd.get("raw") or 0 for rd in readings), default=0)
|
||||||
|
print(f"\n {C.CYAN}{C.BOLD}{sec_name:10}{C.RESET} [{type_name}] "
|
||||||
|
f"ch:{len(readings)} avg:{avg_raw:5.1f} peak:{meter_bar(peak_raw, 15)}")
|
||||||
|
else:
|
||||||
|
result(r)
|
||||||
|
pause()
|
||||||
|
|
||||||
|
elif cmd == "4":
|
||||||
|
section("Tipi meter disponibili")
|
||||||
|
defs = ctrl.METER_DEFS if hasattr(ctrl, 'METER_DEFS') else MockTF5Controller.METER_DEFS
|
||||||
|
r = ctrl.get_meter_types()
|
||||||
|
print()
|
||||||
|
cprint(f" {'SEZIONE':12} {'CH':>5} TIPI DISPONIBILI", C.CYAN, bold=True)
|
||||||
|
cprint(" " + "─" * 50, C.GRAY)
|
||||||
|
if r.get("status") == "success":
|
||||||
|
for sec_name, data in r.get("sections", {}).items():
|
||||||
|
types_str = " ".join(f"{i}={t}" for i, t in data.get("types", {}).items())
|
||||||
|
print(f" {sec_name:12} {data.get('channels', '?'):>5} {types_str}")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
elif cmd == "5":
|
||||||
|
section("Monitor live meter")
|
||||||
|
cprint(" Premi CTRL+C per fermare", C.GRAY)
|
||||||
|
defs = ctrl.METER_DEFS if hasattr(ctrl, 'METER_DEFS') else MockTF5Controller.METER_DEFS
|
||||||
|
sec = ask("Sezione da monitorare", "InCh")
|
||||||
|
if sec not in SECTIONS:
|
||||||
|
err("Sezione non valida"); continue
|
||||||
|
mt = len(defs.get(sec, {}).get("types", [])) - 1
|
||||||
|
interval = ask_float("Intervallo secondi", 1.0)
|
||||||
|
try:
|
||||||
|
iteration = 0
|
||||||
|
while True:
|
||||||
|
r = ctrl.get_all_meters(sec, mt)
|
||||||
|
os.system("clear" if os.name != "nt" else "cls")
|
||||||
|
header(f"LIVE METER — {sec} [{r.get('type_name','?')}]")
|
||||||
|
cprint(f" Aggiornamento #{iteration+1} · {time.strftime('%H:%M:%S')}", C.GRAY)
|
||||||
|
print()
|
||||||
|
if r.get("status") == "success":
|
||||||
|
for rd in r.get("readings", []):
|
||||||
|
ch = rd["channel"]
|
||||||
|
raw = rd.get("raw")
|
||||||
|
db = rd.get("level_db")
|
||||||
|
db_str = f"{db:+6.1f}" if db is not None and db != float('-inf') else " -inf"
|
||||||
|
bar = meter_bar(raw, 25)
|
||||||
|
print(f" ch{ch:02d} {db_str} dBFS {bar}")
|
||||||
|
time.sleep(interval)
|
||||||
|
iteration += 1
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ok("Monitor fermato")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU CANALI
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_channels(ctrl):
|
||||||
|
while True:
|
||||||
|
header("CANALI INPUT (InCh)")
|
||||||
|
cprint(" 1) Info canale", C.WHITE)
|
||||||
|
cprint(" 2) Imposta livello fader", C.WHITE)
|
||||||
|
cprint(" 3) Abilita / Disabilita canale", C.WHITE)
|
||||||
|
cprint(" 4) Imposta pan", C.WHITE)
|
||||||
|
cprint(" 5) Riepilogo tutti i canali", C.WHITE)
|
||||||
|
cprint(" 6) Cerca canale per nome", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
ch = ask_int("Numero canale", 1, 1, 40)
|
||||||
|
result(ctrl.get_channel_info(ch))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
ch = ask_int("Numero canale", 1, 1, 40)
|
||||||
|
db = ask_float("Livello dB (es. -10, 0, +5)", -10)
|
||||||
|
result(ctrl.set_channel_level(ch, db))
|
||||||
|
pause()
|
||||||
|
elif cmd == "3":
|
||||||
|
ch = ask_int("Numero canale", 1, 1, 40)
|
||||||
|
state = ask_bool("Abilitare il canale?", True)
|
||||||
|
result(ctrl.set_channel_on_off(ch, state))
|
||||||
|
pause()
|
||||||
|
elif cmd == "4":
|
||||||
|
ch = ask_int("Numero canale", 1, 1, 40)
|
||||||
|
pan = ask_int("Pan (-63=L, 0=C, +63=R)", 0, -63, 63)
|
||||||
|
result(ctrl.set_channel_pan(ch, pan))
|
||||||
|
pause()
|
||||||
|
elif cmd == "5":
|
||||||
|
r = ctrl.get_all_channels_summary()
|
||||||
|
if r.get("status") == "success":
|
||||||
|
print()
|
||||||
|
cprint(f" {'CH':>4} {'NOME':15} {'ON':>4} {'dB':>8}", C.CYAN, bold=True)
|
||||||
|
cprint(" " + "─" * 40, C.GRAY)
|
||||||
|
for ch in r.get("channels", []):
|
||||||
|
on_str = f"{C.GREEN}ON {C.RESET}" if ch["on"] else f"{C.RED}OFF{C.RESET}"
|
||||||
|
db = ch.get("level_db", -120)
|
||||||
|
db_str = f"{db:+6.1f}" if db > -120 else " -inf"
|
||||||
|
print(f" {ch['channel']:>4} {ch['name']:15} {on_str} {db_str} dB")
|
||||||
|
pause()
|
||||||
|
elif cmd == "6":
|
||||||
|
term = ask("Nome da cercare")
|
||||||
|
result(ctrl.search_channels_by_name(term))
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU MIX BUS
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_mixes(ctrl):
|
||||||
|
while True:
|
||||||
|
header("MIX BUSES")
|
||||||
|
cprint(" 1) Info mix bus", C.WHITE)
|
||||||
|
cprint(" 2) Imposta livello fader", C.WHITE)
|
||||||
|
cprint(" 3) Abilita / Disabilita mix bus", C.WHITE)
|
||||||
|
cprint(" 4) Riepilogo tutti i mix", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
m = ask_int("Numero mix", 1, 1, 20)
|
||||||
|
result(ctrl.get_mix_info(m))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
m = ask_int("Numero mix", 1, 1, 20)
|
||||||
|
db = ask_float("Livello dB", 0)
|
||||||
|
result(ctrl.set_mix_level(m, db))
|
||||||
|
pause()
|
||||||
|
elif cmd == "3":
|
||||||
|
m = ask_int("Numero mix", 1, 1, 20)
|
||||||
|
state = ask_bool("Abilitare?", True)
|
||||||
|
result(ctrl.set_mix_on_off(m, state))
|
||||||
|
pause()
|
||||||
|
elif cmd == "4":
|
||||||
|
r = ctrl.get_all_mixes_summary()
|
||||||
|
if r.get("status") == "success":
|
||||||
|
print()
|
||||||
|
cprint(f" {'MIX':>5} {'NOME':15} {'ON':>4} {'dB':>8}", C.CYAN, bold=True)
|
||||||
|
cprint(" " + "─" * 40, C.GRAY)
|
||||||
|
for m in r.get("mixes", []):
|
||||||
|
on_str = f"{C.GREEN}ON {C.RESET}" if m["on"] else f"{C.RED}OFF{C.RESET}"
|
||||||
|
db = m.get("level_db", -120)
|
||||||
|
db_str = f"{db:+6.1f}" if db > -120 else " -inf"
|
||||||
|
print(f" {m['mix']:>5} {m['name']:15} {on_str} {db_str} dB")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU DCA
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_dca(ctrl):
|
||||||
|
while True:
|
||||||
|
header("DCA GROUPS")
|
||||||
|
cprint(" 1) Info DCA", C.WHITE)
|
||||||
|
cprint(" 2) Imposta livello", C.WHITE)
|
||||||
|
cprint(" 3) Abilita / Disabilita DCA", C.WHITE)
|
||||||
|
cprint(" 4) Riepilogo tutti i DCA", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
d = ask_int("Numero DCA", 1, 1, 8)
|
||||||
|
result(ctrl.get_dca_info(d))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
d = ask_int("Numero DCA", 1, 1, 8)
|
||||||
|
db = ask_float("Livello dB", 0)
|
||||||
|
result(ctrl.set_dca_level(d, db))
|
||||||
|
pause()
|
||||||
|
elif cmd == "3":
|
||||||
|
d = ask_int("Numero DCA", 1, 1, 8)
|
||||||
|
state = ask_bool("Abilitare?", True)
|
||||||
|
result(ctrl.set_dca_on_off(d, state))
|
||||||
|
pause()
|
||||||
|
elif cmd == "4":
|
||||||
|
r = ctrl.get_all_dca_summary()
|
||||||
|
if r.get("status") == "success":
|
||||||
|
print()
|
||||||
|
cprint(f" {'DCA':>5} {'NOME':15} {'ON':>4} {'dB':>8}", C.CYAN, bold=True)
|
||||||
|
cprint(" " + "─" * 40, C.GRAY)
|
||||||
|
for d in r.get("dcas", []):
|
||||||
|
on_str = f"{C.GREEN}ON {C.RESET}" if d["on"] else f"{C.RED}OFF{C.RESET}"
|
||||||
|
db = d.get("level_db", 0)
|
||||||
|
print(f" {d['dca']:>5} {d['name']:15} {on_str} {db:+6.1f} dB")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU BUS MASTER
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_masters(ctrl):
|
||||||
|
while True:
|
||||||
|
header("BUS MASTER (Stereo / Mono)")
|
||||||
|
cprint(" 1) Info Stereo bus", C.WHITE)
|
||||||
|
cprint(" 2) Imposta livello Stereo", C.WHITE)
|
||||||
|
cprint(" 3) Info Mono bus", C.WHITE)
|
||||||
|
cprint(" 4) Imposta livello Mono", C.WHITE)
|
||||||
|
cprint(" 5) Mute Masters — stato", C.WHITE)
|
||||||
|
cprint(" 6) Attiva / Disattiva Mute Master group", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
bus = ask_int("Bus stereo (1=L, 2=R)", 1, 1, 2)
|
||||||
|
result(ctrl.get_stereo_info(bus))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
bus = ask_int("Bus stereo (1=L, 2=R)", 1, 1, 2)
|
||||||
|
db = ask_float("Livello dB", 0)
|
||||||
|
result(ctrl.set_stereo_level(bus, db))
|
||||||
|
pause()
|
||||||
|
elif cmd == "3":
|
||||||
|
result(ctrl.get_mono_info())
|
||||||
|
pause()
|
||||||
|
elif cmd == "4":
|
||||||
|
db = ask_float("Livello dB", -6)
|
||||||
|
result(ctrl.set_mono_level(db))
|
||||||
|
pause()
|
||||||
|
elif cmd == "5":
|
||||||
|
result(ctrl.get_all_mute_masters_summary())
|
||||||
|
pause()
|
||||||
|
elif cmd == "6":
|
||||||
|
g = ask_int("Numero gruppo (1-6)", 1, 1, 6)
|
||||||
|
state = ask_bool("Attivare il mute?", False)
|
||||||
|
result(ctrl.set_mute_master(g, state))
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU COMANDI RAW
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_raw(ctrl):
|
||||||
|
"""Invia comandi raw al mixer (solo modalità reale)."""
|
||||||
|
if isinstance(ctrl, MockTF5Controller):
|
||||||
|
warn("I comandi raw non sono disponibili in modalità simulazione.")
|
||||||
|
pause()
|
||||||
|
return
|
||||||
|
|
||||||
|
header("COMANDI RAW")
|
||||||
|
cprint(" Invia comandi testuali diretti al mixer.", C.GRAY)
|
||||||
|
cprint(" Esempi:", C.GRAY)
|
||||||
|
cprint(" get MIXER:Current/InCh/Fader/Level 0 0", C.GRAY)
|
||||||
|
cprint(" set MIXER:Current/InCh/Fader/On 0 0 0", C.GRAY)
|
||||||
|
cprint(" get MIXER:Current/Meter/InCh 0 2", C.GRAY)
|
||||||
|
cprint(" Digita 'exit' per uscire.", C.GRAY)
|
||||||
|
print()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
cmd = input(f"{C.MAGENTA} RAW> {C.RESET}").strip()
|
||||||
|
if not cmd:
|
||||||
|
continue
|
||||||
|
if cmd.lower() in ("exit", "quit", "0"):
|
||||||
|
break
|
||||||
|
response = ctrl._send_command(cmd)
|
||||||
|
cprint(f" ← {response}", C.YELLOW)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU SCENE
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_scenes(ctrl):
|
||||||
|
while True:
|
||||||
|
header("SCENE")
|
||||||
|
cprint(" 1) Richiama scena (banco A o B)", C.WHITE)
|
||||||
|
cprint(" 2) Refresh cache", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
bank = ask("Banco (a/b)", "a").lower()
|
||||||
|
num = ask_int("Numero scena (0-99)", 0, 0, 99)
|
||||||
|
result(ctrl.recall_scene(bank, num))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
ok("Avvio refresh cache...")
|
||||||
|
result(ctrl.refresh_cache())
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MENU ALTRI CANALI
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def menu_other_channels(ctrl):
|
||||||
|
while True:
|
||||||
|
header("ALTRI CANALI")
|
||||||
|
cprint(" 1) Stereo Input (StInCh) — info", C.WHITE)
|
||||||
|
cprint(" 2) FX Return (FxRtnCh) — info", C.WHITE)
|
||||||
|
cprint(" 0) ← Torna al menu principale", C.GRAY)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta")
|
||||||
|
|
||||||
|
if cmd == "0":
|
||||||
|
break
|
||||||
|
elif cmd == "1":
|
||||||
|
ch = ask_int("Canale StInCh (1-4)", 1, 1, 4)
|
||||||
|
result(ctrl.get_steinch_info(ch))
|
||||||
|
pause()
|
||||||
|
elif cmd == "2":
|
||||||
|
ch = ask_int("Canale FxRtnCh (1-4)", 1, 1, 4)
|
||||||
|
result(ctrl.get_fxrtn_info(ch))
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# HELP / REFERENCE
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def show_help():
|
||||||
|
header("REFERENCE — PROTOCOLLO TF5")
|
||||||
|
section("Struttura comandi")
|
||||||
|
print("""
|
||||||
|
get <PATH> <IDX1> <IDX2> → legge un parametro
|
||||||
|
set <PATH> <IDX1> <IDX2> <V> → scrive un parametro
|
||||||
|
ssrecall_ex scene_a <N> → richiama scena banco A
|
||||||
|
|
||||||
|
Risposta OK: OK <valore>
|
||||||
|
Risposta ERR: ERROR <codice> <messaggio>
|
||||||
|
Notify: NOTIFY <PATH> <IDX1> <IDX2> <V>
|
||||||
|
""")
|
||||||
|
section("Livelli fader")
|
||||||
|
print("""
|
||||||
|
Valore interno: intero × 100 (es. -1000 = -10.0 dB)
|
||||||
|
Minimo: -32768 = -∞ (fader giù)
|
||||||
|
Massimo: +1000 = +10.0 dB
|
||||||
|
Unity gain: 0 = 0.0 dB
|
||||||
|
""")
|
||||||
|
section("Meter")
|
||||||
|
print("""
|
||||||
|
Valore raw: 0–127 (intero)
|
||||||
|
Conversione: dBFS = (raw/127) × 90 - 72
|
||||||
|
0 → -inf dBFS
|
||||||
|
76 → ~ 0 dBFS (approssimato)
|
||||||
|
127→ +18 dBFS
|
||||||
|
|
||||||
|
SEZIONI METER:
|
||||||
|
┌──────────┬─────┬────────────────────────────────────┐
|
||||||
|
│ InCh │ 40 │ 0=PreHPF 1=PreFader 2=PostOn │
|
||||||
|
│ StInCh │ 4 │ 0=PreEQ 1=PreFader 2=PostOn │
|
||||||
|
│ FxRtnCh │ 4 │ 0=PreFader 1=PostOn │
|
||||||
|
│ Mix │ 20 │ 0=PreEQ 1=PreFader 2=PostOn │
|
||||||
|
│ Mtrx │ 4 │ 0=PreEQ 1=PreFader 2=PostOn │
|
||||||
|
│ St │ 2 │ 0=PreEQ 1=PreFader 2=PostOn │
|
||||||
|
│ Mono │ 1 │ 0=PreEQ 1=PreFader 2=PostOn │
|
||||||
|
└──────────┴─────┴────────────────────────────────────┘
|
||||||
|
""")
|
||||||
|
section("Pan")
|
||||||
|
print("""
|
||||||
|
Valore: -63 (full left) → 0 (center) → +63 (full right)
|
||||||
|
""")
|
||||||
|
pause()
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# MAIN / SETUP
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
def startup_screen():
|
||||||
|
os.system("clear" if os.name != "nt" else "cls")
|
||||||
|
print()
|
||||||
|
cprint("╔══════════════════════════════════════════════════════════╗", C.CYAN)
|
||||||
|
cprint("║ ║", C.CYAN)
|
||||||
|
cprint("║ YAMAHA TF5 — CLI INTERATTIVO DI TEST ║", C.CYAN, bold=True)
|
||||||
|
cprint("║ Socket Protocol Explorer ║", C.CYAN)
|
||||||
|
cprint("║ ║", C.CYAN)
|
||||||
|
cprint("╚══════════════════════════════════════════════════════════╝", C.CYAN)
|
||||||
|
print()
|
||||||
|
|
||||||
|
def choose_mode():
|
||||||
|
cprint(" Modalità di connessione:", C.WHITE, bold=True)
|
||||||
|
cprint(" 1) SIMULAZIONE (mock — nessun mixer necessario)", C.GREEN)
|
||||||
|
cprint(" 2) REALE (connessione socket al TF5)", C.YELLOW)
|
||||||
|
print()
|
||||||
|
choice = ask("Scelta", "1")
|
||||||
|
|
||||||
|
if choice == "2":
|
||||||
|
host = ask("Indirizzo IP del mixer", "192.168.0.128")
|
||||||
|
port = ask_int("Porta", 49280, 1, 65535)
|
||||||
|
info(f"Connessione a {host}:{port}...")
|
||||||
|
ctrl, error = try_load_real_controller(host, port)
|
||||||
|
if ctrl is None:
|
||||||
|
err(f"Impossibile connettersi: {error}")
|
||||||
|
warn("Avvio in modalità simulazione come fallback.")
|
||||||
|
return MockTF5Controller()
|
||||||
|
ok(f"Connesso a {host}:{port}")
|
||||||
|
return ctrl
|
||||||
|
else:
|
||||||
|
return MockTF5Controller()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
startup_screen()
|
||||||
|
ctrl = choose_mode()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
header("MENU PRINCIPALE")
|
||||||
|
cprint(f" Modalità: {'SIMULAZIONE (mock)' if isinstance(ctrl, MockTF5Controller) else 'REALE'}", C.GRAY)
|
||||||
|
print()
|
||||||
|
cprint(" M) Meter reading ──────────── leggi livelli meter", C.MAGENTA, bold=True)
|
||||||
|
cprint(" C) Canali input ────────────── InCh 1-40", C.WHITE)
|
||||||
|
cprint(" X) Mix buses ───────────────── MX 1-20", C.WHITE)
|
||||||
|
cprint(" D) DCA groups ──────────────── DCA 1-8", C.WHITE)
|
||||||
|
cprint(" B) Bus master ──────────────── Stereo / Mono / Mute", C.WHITE)
|
||||||
|
cprint(" A) Altri canali ────────────── StInCh / FxRtnCh", C.WHITE)
|
||||||
|
cprint(" S) Scene ───────────────────── recall / store", C.WHITE)
|
||||||
|
cprint(" R) Comandi RAW ─────────────── protocollo diretto", C.WHITE)
|
||||||
|
cprint(" H) Help / Reference ────────── documentazione", C.GRAY)
|
||||||
|
cprint(" Q) Esci", C.RED)
|
||||||
|
|
||||||
|
cmd = ask("\nScelta").upper()
|
||||||
|
|
||||||
|
if cmd == "Q":
|
||||||
|
cprint("\n Chiusura in corso...", C.GRAY)
|
||||||
|
ctrl.close()
|
||||||
|
cprint(" Arrivederci! 🎚️\n", C.CYAN)
|
||||||
|
break
|
||||||
|
elif cmd == "M":
|
||||||
|
menu_meter(ctrl)
|
||||||
|
elif cmd == "C":
|
||||||
|
menu_channels(ctrl)
|
||||||
|
elif cmd == "X":
|
||||||
|
menu_mixes(ctrl)
|
||||||
|
elif cmd == "D":
|
||||||
|
menu_dca(ctrl)
|
||||||
|
elif cmd == "B":
|
||||||
|
menu_masters(ctrl)
|
||||||
|
elif cmd == "A":
|
||||||
|
menu_other_channels(ctrl)
|
||||||
|
elif cmd == "S":
|
||||||
|
menu_scenes(ctrl)
|
||||||
|
elif cmd == "R":
|
||||||
|
menu_raw(ctrl)
|
||||||
|
elif cmd == "H":
|
||||||
|
show_help()
|
||||||
|
else:
|
||||||
|
warn("Scelta non valida")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(f"\n\n{C.GRAY} Interrotto dall'utente.{C.RESET}\n")
|
||||||
|
sys.exit(0)
|
||||||
Reference in New Issue
Block a user