meter
This commit is contained in:
@@ -1582,4 +1582,215 @@ class TF5MixerController:
|
||||
command = f"set MIXER:Lib/Bank/Scene/Store {1 if bank.lower() == 'b' else 0} {scene_number}"
|
||||
response = self._send_command(command)
|
||||
return {"status": "success" if "OK" in response else "error",
|
||||
"message": f"Scena salvata nel banco {bank.upper()}{scene_number}.", "response": response}
|
||||
"message": f"Scena salvata nel banco {bank.upper()}{scene_number}.", "response": response}
|
||||
|
||||
# =========================================================================
|
||||
# METER READING
|
||||
# Riferimento prminfo:
|
||||
# 2000 InCh 40ch 3 tipi: 0=PreHPF, 1=PreFader, 2=PostOn
|
||||
# 2001 StInCh 4ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn
|
||||
# 2002 FxRtnCh 4ch 2 tipi: 0=PreFader, 1=PostOn
|
||||
# 2100 Mix 20ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn
|
||||
# 2101 Mtrx 4ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn
|
||||
# 2102 St 2ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn
|
||||
# 2103 Mono 1ch 3 tipi: 0=PreEQ, 1=PreFader, 2=PostOn
|
||||
#
|
||||
# Il valore raw è un intero 0–127.
|
||||
# Conversione: 0 = -inf dB, 1–127 lineare su scala ~-72..+18 dBFS.
|
||||
# Formula Yamaha TF: dBFS = (raw / 127) * 90 - 72 (approssimazione lineare)
|
||||
# Oppure si restituisce direttamente il raw per display proporzionale.
|
||||
# =========================================================================
|
||||
|
||||
# Mappa dei meter disponibili
|
||||
_METER_DEFS = {
|
||||
"InCh": {"path": "MIXER:Current/Meter/InCh", "channels": 40, "types": ["PreHPF", "PreFader", "PostOn"]},
|
||||
"StInCh": {"path": "MIXER:Current/Meter/StInCh", "channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||
"FxRtnCh": {"path": "MIXER:Current/Meter/FxRtnCh", "channels": 4, "types": ["PreFader", "PostOn"]},
|
||||
"Mix": {"path": "MIXER:Current/Meter/Mix", "channels": 20, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||
"Mtrx": {"path": "MIXER:Current/Meter/Mtrx", "channels": 4, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||
"St": {"path": "MIXER:Current/Meter/St", "channels": 2, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||
"Mono": {"path": "MIXER:Current/Meter/Mono", "channels": 1, "types": ["PreEQ", "PreFader", "PostOn"]},
|
||||
}
|
||||
|
||||
def _meter_raw_to_db(self, raw: int) -> float:
|
||||
"""
|
||||
Converte il valore raw del meter (0-127) in dBFS approssimato.
|
||||
Scala Yamaha TF: 127 = +18 dBFS, 76 = 0 dBFS, 1 = -72 dBFS, 0 = -inf.
|
||||
"""
|
||||
if raw <= 0:
|
||||
return float('-inf')
|
||||
# Interpolazione lineare: 0..127 → -72..+18 dBFS (range 90 dB)
|
||||
return round((raw / 127.0) * 90.0 - 72.0, 1)
|
||||
|
||||
def _parse_meter_response(self, response: str) -> Optional[int]:
|
||||
"""Estrae il valore intero raw dal response di un comando get meter."""
|
||||
try:
|
||||
first_line = response.split('\n')[0].strip()
|
||||
parts = first_line.split()
|
||||
if parts and parts[0] == "OK":
|
||||
return int(parts[-1])
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_meter(self, section: str, channel: int, meter_type: int = 2) -> dict:
|
||||
"""
|
||||
Legge il livello meter di un singolo canale.
|
||||
|
||||
Args:
|
||||
section: Sezione del mixer: "InCh", "StInCh", "FxRtnCh",
|
||||
"Mix", "Mtrx", "St", "Mono"
|
||||
channel: Numero canale (1-based)
|
||||
meter_type: Indice del tipo meter (0-based, vedi _METER_DEFS per i nomi)
|
||||
|
||||
Returns:
|
||||
dict con raw (0-127), level_db (dBFS), section, channel, type_name
|
||||
"""
|
||||
if section not in self._METER_DEFS:
|
||||
return {"status": "error",
|
||||
"message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"}
|
||||
|
||||
defn = self._METER_DEFS[section]
|
||||
num_ch = defn["channels"]
|
||||
num_types = len(defn["types"])
|
||||
|
||||
if not 1 <= channel <= num_ch:
|
||||
return {"status": "error", "message": f"Canale {section} deve essere tra 1 e {num_ch}"}
|
||||
if not 0 <= meter_type < num_types:
|
||||
return {"status": "error",
|
||||
"message": f"Tipo meter deve essere tra 0 e {num_types - 1} per {section}: {defn['types']}"}
|
||||
|
||||
ch_idx = channel - 1
|
||||
command = f"get {defn['path']} {ch_idx} {meter_type}"
|
||||
response = self._send_command(command)
|
||||
raw = self._parse_meter_response(response)
|
||||
|
||||
if raw is None:
|
||||
return {"status": "error", "message": f"Risposta non valida: {response}"}
|
||||
|
||||
level_db = self._meter_raw_to_db(raw)
|
||||
type_name = defn["types"][meter_type]
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"section": section,
|
||||
"channel": channel,
|
||||
"type": meter_type,
|
||||
"type_name": type_name,
|
||||
"raw": raw,
|
||||
"level_db": level_db,
|
||||
"message": f"{section} ch{channel} [{type_name}]: {raw}/127 → {level_db} dBFS"
|
||||
}
|
||||
|
||||
def get_all_meters(self, section: str, meter_type: int = 2) -> dict:
|
||||
"""
|
||||
Legge tutti i canali meter di una sezione.
|
||||
|
||||
Args:
|
||||
section: "InCh", "StInCh", "FxRtnCh", "Mix", "Mtrx", "St", "Mono"
|
||||
meter_type: Indice tipo meter (0-based)
|
||||
|
||||
Returns:
|
||||
dict con lista di letture meter per tutti i canali della sezione
|
||||
"""
|
||||
if section not in self._METER_DEFS:
|
||||
return {"status": "error",
|
||||
"message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"}
|
||||
|
||||
defn = self._METER_DEFS[section]
|
||||
num_ch = defn["channels"]
|
||||
num_types = len(defn["types"])
|
||||
|
||||
if not 0 <= meter_type < num_types:
|
||||
return {"status": "error",
|
||||
"message": f"Tipo meter deve essere tra 0 e {num_types - 1} per {section}: {defn['types']}"}
|
||||
|
||||
type_name = defn["types"][meter_type]
|
||||
readings = []
|
||||
|
||||
for ch in range(1, num_ch + 1):
|
||||
ch_idx = ch - 1
|
||||
command = f"get {defn['path']} {ch_idx} {meter_type}"
|
||||
response = self._send_command(command)
|
||||
raw = self._parse_meter_response(response)
|
||||
if raw is not None:
|
||||
level_db = self._meter_raw_to_db(raw)
|
||||
readings.append({
|
||||
"channel": ch,
|
||||
"raw": raw,
|
||||
"level_db": level_db
|
||||
})
|
||||
else:
|
||||
readings.append({
|
||||
"channel": ch,
|
||||
"raw": None,
|
||||
"level_db": None,
|
||||
"error": response
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"section": section,
|
||||
"type": meter_type,
|
||||
"type_name": type_name,
|
||||
"total_channels": num_ch,
|
||||
"readings": readings,
|
||||
"message": f"Meter {section} [{type_name}]: {num_ch} canali letti"
|
||||
}
|
||||
|
||||
def get_meter_snapshot(self, meter_type: int = 2) -> dict:
|
||||
"""
|
||||
Legge un snapshot dei meter di tutte le sezioni principali.
|
||||
|
||||
Args:
|
||||
meter_type: Tipo meter da usare per tutte le sezioni.
|
||||
Nota: FxRtnCh ha solo 2 tipi (0-1), se si passa 2
|
||||
viene automaticamente usato il tipo 1 (PostOn).
|
||||
|
||||
Returns:
|
||||
dict con un sotto-dict per ogni sezione, contenente le letture.
|
||||
"""
|
||||
snapshot = {}
|
||||
for section, defn in self._METER_DEFS.items():
|
||||
# Clamp meter_type al massimo disponibile per la sezione
|
||||
safe_type = min(meter_type, len(defn["types"]) - 1)
|
||||
result = self.get_all_meters(section, safe_type)
|
||||
snapshot[section] = result
|
||||
|
||||
sections_ok = sum(1 for r in snapshot.values() if r["status"] == "success")
|
||||
return {
|
||||
"status": "success",
|
||||
"meter_type_requested": meter_type,
|
||||
"sections": snapshot,
|
||||
"message": f"Snapshot meter: {sections_ok}/{len(self._METER_DEFS)} sezioni lette"
|
||||
}
|
||||
|
||||
def get_meter_types(self, section: str = None) -> dict:
|
||||
"""
|
||||
Restituisce i tipi di meter disponibili per una sezione (o tutte).
|
||||
|
||||
Args:
|
||||
section: Nome sezione opzionale. Se None, restituisce tutte.
|
||||
"""
|
||||
if section:
|
||||
if section not in self._METER_DEFS:
|
||||
return {"status": "error",
|
||||
"message": f"Sezione '{section}' non valida. Valori: {list(self._METER_DEFS.keys())}"}
|
||||
defn = self._METER_DEFS[section]
|
||||
return {
|
||||
"status": "success",
|
||||
"section": section,
|
||||
"channels": defn["channels"],
|
||||
"types": {i: name for i, name in enumerate(defn["types"])}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status": "success",
|
||||
"sections": {
|
||||
sec: {
|
||||
"channels": d["channels"],
|
||||
"types": {i: name for i, name in enumerate(d["types"])}
|
||||
}
|
||||
for sec, d in self._METER_DEFS.items()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user