console.log("App.js caricato - VU Meter & Click-to-Tune attivi!"); let ws = null; let audioCtx = null; let gainNode = null; let analyser = null; let dataArray = null; let nextStartTime = 0; const connectBtn = document.getElementById('connectBtn'); const tgInput = document.getElementById('tgInput'); const setTgBtn = document.getElementById('setTgBtn'); const displayScreen = document.getElementById('displayScreen'); const statusText = document.getElementById('statusText'); const volumeSlider = document.getElementById('volumeSlider'); const volumeValue = document.getElementById('volumeValue'); const liveTrafficList = document.getElementById('liveTrafficList'); const vuMeterBar = document.getElementById('vuMeterBar'); if(statusText) statusText.innerText = "✅ Sistema pronto. Clicca Avvia Ascolto."; if(volumeSlider) { volumeSlider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); if(volumeValue) volumeValue.innerText = Math.round(val * 100) + '%'; if (gainNode) gainNode.gain.value = val; }); } window.setQuickTg = function(valore) { if (tgInput) tgInput.value = valore; inviaCambioTG(valore); }; function inviaCambioTG(nuovoTG) { if (!nuovoTG || nuovoTG <= 0) return; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ action: "cambia_tg", tg: nuovoTG })); if(displayScreen) displayScreen.innerText = "SINTONIA..."; if(statusText) statusText.innerText = "Sintonizzato su TG " + nuovoTG; } else { if(statusText) statusText.innerText = "Pronto a sintonizzare TG " + nuovoTG + " (Avvia prima)"; } } // --- VU METER LOOP --- function renderVUMeter() { requestAnimationFrame(renderVUMeter); if (!analyser || !vuMeterBar) return; analyser.getByteTimeDomainData(dataArray); let sum = 0; for(let i = 0; i < dataArray.length; i++) { let v = (dataArray[i] - 128) / 128.0; sum += v * v; } let rms = Math.sqrt(sum / dataArray.length); let percent = Math.min(100, rms * 500); // Moltiplicatore di sensibilità grafica vuMeterBar.style.width = percent + '%'; if (percent > 85) vuMeterBar.style.background = '#e74c3c'; // Rosso saturo else if (percent > 50) vuMeterBar.style.background = '#f1c40f'; // Giallo else vuMeterBar.style.background = '#2ecc71'; // Verde standard } function playPCM320(arrayBuffer) { if (!audioCtx || !analyser) return; try { const int16Array = new Int16Array(arrayBuffer); const float32Array = new Float32Array(int16Array.length); for (let i = 0; i < int16Array.length; i++) { float32Array[i] = int16Array[i] / 32768.0; } const buffer = audioCtx.createBuffer(1, float32Array.length, 8000); buffer.getChannelData(0).set(float32Array); const source = audioCtx.createBufferSource(); source.buffer = buffer; // Collega al VU meter, che a sua volta è collegato al volume source.connect(analyser); const currentTime = audioCtx.currentTime; if (nextStartTime < currentTime) nextStartTime = currentTime + 0.02; source.start(nextStartTime); nextStartTime += buffer.duration; } catch(err) { console.error("Errore audio:", err); } } function startAudio() { try { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioCtx.createAnalyser(); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); gainNode = audioCtx.createGain(); // Catena: Source -> Analyser(VU) -> Gain(Volume) -> Casse analyser.connect(gainNode); gainNode.connect(audioCtx.destination); if(volumeSlider) gainNode.gain.value = parseFloat(volumeSlider.value); renderVUMeter(); // Avvia l'animazione grafica } if (audioCtx.state === 'suspended') audioCtx.resume(); if(statusText) statusText.innerText = "🔄 Connessione in corso..."; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(`${protocol}//${window.location.host}/ws`); ws.binaryType = 'arraybuffer'; ws.onopen = () => { if(connectBtn) connectBtn.style.display = 'none'; if(tgInput) inviaCambioTG(tgInput.value); }; ws.onmessage = (event) => { if (event.data instanceof ArrayBuffer) { playPCM320(event.data); } else if (typeof event.data === 'string') { const data = JSON.parse(event.data); if (data.action === "error") return; if (data.action === "display") { if(displayScreen) { displayScreen.innerText = data.text; displayScreen.style.background = data.active ? "#e67e22" : "#1abc9c"; } if(statusText) statusText.style.color = "#bdc3c7"; } // CLICK-TO-TUNE IMPLEMENTATO QUI if (data.action === "global_traffic" && liveTrafficList) { if (!data.data || data.data.length === 0) { liveTrafficList.innerHTML = "Nessun traffico in corso..."; } else { liveTrafficList.innerHTML = data.data.map(item => `