Files
Automixbot/mixer_agent.py
2025-10-27 20:13:23 +01:00

267 lines
10 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
TF5 Mixer AI Agent - Controllo del mixer Yamaha TF5 tramite linguaggio naturale
usando Google Gemini con function calling.
"""
import socket
import sys
import os
import json
import time
from pathlib import Path
from typing import List, Optional
from google import genai
from google.genai import types
from dotenv import load_dotenv
from mixer_controller import TF5MixerController
load_dotenv()
from config import *
class TF5AIAgent:
"""Agente AI per controllare il mixer TF5 con linguaggio naturale."""
def __init__(self, mixer_host=DEFAULT_HOST, mixer_port=DEFAULT_PORT):
self.controller = TF5MixerController(mixer_host, mixer_port)
# Configura il client Gemini
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError("GEMINI_API_KEY non trovata nelle variabili d'ambiente")
self.client = genai.Client(api_key=api_key)
# Configura gli strumenti con automatic function calling
self.config = types.GenerateContentConfig(
tools=[
self.controller.recall_scene,
self.controller.set_channel_level,
self.controller.set_channel_on_off,
self.controller.set_channel_pan,
self.controller.set_mix_level,
self.controller.set_mix_on_off,
self.controller.mute_multiple_channels,
self.controller.unmute_multiple_channels,
self.controller.get_channel_info,
self.controller.get_mix_info,
self.controller.search_channels_by_name,
self.controller.search_mixes_by_name,
self.controller.get_all_channels_summary,
self.controller.get_all_mixes_summary,
self.controller.refresh_cache,
],
temperature=0,
)
# Messaggio di sistema per dare contesto all'AI
self.system_instruction = """Sei un assistente per il controllo del mixer audio Yamaha TF5.
Parli in modo semplice e diretto, come un tecnico del suono esperto che aiuta i musicisti sul palco.
Il mixer ha:
- 40 canali (microfoni, strumenti, ecc.)
- 20 mix/aux (monitor, effetti, ecc.)
- Ogni canale ha volume (da silenzio a +10 dB), acceso/spento, e bilanciamento sinistra/destra
- Scene memorizzate nei banchi A e B (da 0 a 99)
IMPORTANTE - Sistema di Cache:
- Le info sui canali e mix sono salvate per 60 minuti per non sovraccaricare il mixer
- Usa search_channels_by_name e search_mixes_by_name per cercare velocemente
- Usa get_all_channels_summary e get_all_mixes_summary per vedere tutto
- Quando modifichi un canale/mix, la cache viene aggiornata automaticamente
- Quando si carica una scena, i dati vengono invalidati e aggiornati alla prossima richiesta
- Puoi fare refresh_cache solo se l'utente lo chiede esplicitamente
Come interpretare le richieste:
VOLUME/LIVELLO:
- "alza/abbassa/aumenta/diminuisci" → cambia il volume
- "più/meno forte/volume" → cambia il volume
- "al massimo" → +10 dB
- "un po' più alto" → +3 dB circa
- "metti a zero" o "unity" → 0 dB
- "abbassa di poco" → -3 dB
- "metti basso" → -20 dB
- "silenzio/muto" → spegni il canale
ON/OFF:
- "accendi/attiva/apri" → canale/mix ON
- "spegni/muta/chiudi/stacca" → canale/mix OFF
- "muto" può significare sia spegnere che abbassare molto
BILANCIAMENTO (PAN):
- "a sinistra/left" → pan -63
- "a destra/right" → pan +63
- "al centro" → pan 0
- "un po' a sinistra" → pan -30 circa
IDENTIFICAZIONE CANALI E MIX:
- Accetta sia numeri ("canale 5", "mix 3") che nomi ("il microfono del cantante", "monitor palco")
- Se non trovi un canale/mix per nome, cerca usando search_channels_by_name o search_mixes_by_name
- "il mio mic/microfono" → cerca tra i canali chi è sul palco
- "le chitarre/i vox/le tastiere" → cerca per strumento
- "tutti i mic/tutte le chitarre" → cerca e gestisci multipli
- "il monitor" / "l'aux 2" → cerca tra i mix
SCENE:
- "carica/richiama/vai alla scena X" → recall_scene
- Accetta "A5", "scena A 5", "la cinque del banco A", ecc.
GRUPPI DI CANALI:
- "i canali dal 3 al 7" → canali 3,4,5,6,7
- "spegni tutto tranne..." → muta tutti gli altri
- "solo i microfoni" → attiva solo quelli, spegni il resto
CASI PARTICOLARI:
- 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)
- Conferma sempre cosa hai fatto con un messaggio breve e chiaro
- Usa emoji occasionalmente per rendere le risposte più amichevoli (✅ ❌ 🎤 🎸 🔊)
- Se qualcosa non funziona, spiega il problema in modo semplice
ESEMPI DI INTERPRETAZIONE:
"alza il mio microfono" → cerca canale per nome, aumenta volume di 3-5 dB
"abbassa un po' le chitarre" → cerca canali chitarra, riduci di 3-5 dB
"muto tutto" → spegni tutti i 40 canali
"solo voce" → cerca canali voce, accendi quelli e spegni gli altri
"alza il monitor 2" → cerca mix 2, aumenta volume
"carica la scena del soundcheck" → cerca nel nome o chiedi numero scena
"troppo forte, abbassa" → riduci di 5-8 dB
"spegni questo canale" → se non specifica numero, chiedi quale
Rispondi sempre in modo:
- Diretto e colloquiale
- Senza troppi tecnicismi
- Confermando chiaramente l'azione eseguita
- Suggerendo alternative se qualcosa non è possibile
Ricorda: chi ti parla è spesso sul palco, con le mani occupate da uno strumento.
Devi essere veloce, chiaro e capire anche richieste approssimative.
"""
def __enter__(self):
"""Context manager entry."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit."""
self.controller.close()
def close(self):
"""Chiude le connessioni."""
self.controller.close()
def chat(self, user_message: str) -> str:
"""Invia un messaggio all'agente e riceve la risposta.
Args:
user_message: Il messaggio dell'utente in linguaggio naturale
Returns:
La risposta dell'agente
"""
try:
full_prompt = f"{self.system_instruction}\n\nUtente: {user_message}"
response = self.client.models.generate_content(
model="gemini-2.5-pro",
contents=full_prompt,
config=self.config,
)
return response.text
except Exception as e:
return f"Errore nell'elaborazione della richiesta: {e}"
def interactive_mode(self):
"""Avvia una sessione interattiva con l'agente."""
print("=" * 70)
print("TF5 Mixer AI Agent - Controllo tramite linguaggio naturale")
print("=" * 70)
# Mostra stato cache
cache_age = time.time() - self.controller._cache.get("timestamp", 0)
if self.controller._is_cache_valid():
channels_count = len(self.controller._cache.get("channels", {}))
mixes_count = len(self.controller._cache.get("mixes", {}))
print(f"\n💾 Cache disponibile (età: {int(cache_age)}s)")
print(f" 📊 {channels_count} canali, {mixes_count} mix")
else:
print("\n💾 Cache non disponibile, verrà creata al primo utilizzo")
print("\nEsempi di comandi:")
print(" - 'Alza il canale 5 a -10 dB'")
print(" - 'Spegni i canali dal 1 al 5'")
print(" - 'Richiama la scena A10'")
print(" - 'Imposta il pan del canale 3 a sinistra'")
print(" - 'Muta i canali 2, 4, 6 e 8'")
print(" - 'Alza il mix 3 di 5 dB'")
print(" - 'Quali canali sono associati ai vox?'")
print(" - 'Mostrami lo stato del canale 12'")
print(" - 'Cerca i monitor'")
print(" - 'Aggiorna i dati dal mixer'")
print("\nDigita 'esci' o 'quit' per terminare\n")
while True:
try:
user_input = input("\n🎛️ Tu: ").strip()
if user_input.lower() in ['esci', 'quit', 'exit', 'q']:
print("\n👋 Arrivederci!")
break
if not user_input:
continue
print("\n🤖 Agent: ", end="", flush=True)
response = self.chat(user_input)
print(response)
except KeyboardInterrupt:
print("\n\n👋 Arrivederci!")
break
except Exception as e:
print(f"\n❌ Errore: {e}")
def main():
"""Funzione principale."""
import argparse
parser = argparse.ArgumentParser(
description='TF5 Mixer AI Agent - Controllo tramite linguaggio naturale'
)
parser.add_argument('--host', default=DEFAULT_HOST, help=f'IP del mixer (default: {DEFAULT_HOST})')
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help=f'Porta (default: {DEFAULT_PORT})')
parser.add_argument('--message', '-m', help='Invia un singolo comando invece di avviare la modalità interattiva')
parser.add_argument('--refresh-cache', action='store_true', help='Forza l\'aggiornamento della cache all\'avvio')
args = parser.parse_args()
# Verifica che la API key sia impostata
if not os.getenv("GEMINI_API_KEY"):
print("❌ Errore: GEMINI_API_KEY non trovata nelle variabili d'ambiente")
print("\nPer impostare la chiave API:")
print(" export GEMINI_API_KEY='la-tua-chiave-api'")
print("\nOttieni una chiave API gratuita su: https://aistudio.google.com/apikey")
sys.exit(1)
try:
with TF5AIAgent(args.host, args.port) as agent:
if args.refresh_cache:
agent.controller.refresh_cache()
if args.message:
print(f"🤖 Risposta: {agent.chat(args.message)}")
else:
agent.interactive_mode()
except Exception as e:
print(f"❌ Errore fatale: {e}")
sys.exit(1)
if __name__ == '__main__':
main()