Преглед изворни кода

Live listen on signal click + highlight

master
Jan Svabenik пре 11 часа
родитељ
комит
045b2005c2
2 измењених фајлова са 88 додато и 37 уклоњено
  1. +87
    -37
      web/app.js
  2. +1
    -0
      web/style.css

+ 87
- 37
web/app.js Прегледај датотеку

@@ -118,6 +118,7 @@ let latest = null;
let currentConfig = null;
let liveAudio = null;
let liveListenWS = null; // WebSocket-based live listen
let liveListenTarget = null; // { freq, bw, mode }
let stats = { buffer_samples: 0, dropped: 0, resets: 0, last_sample_ago_ms: -1 };

// ---------------------------------------------------------------------------
@@ -260,6 +261,68 @@ class LiveListenWS {
this.nextTime += audioBuffer.duration;
}
}

function resolveListenMode(detectedMode) {
const manual = listenModeSelect?.value || '';
if (manual) return manual;
return detectedMode || 'NFM';
}

function setLiveListenUI(active) {
if (liveListenBtn) {
liveListenBtn.textContent = active ? '■ Stop' : 'Live Listen';
liveListenBtn.classList.toggle('active', active);
}
if (liveListenEventBtn) {
liveListenEventBtn.textContent = active ? '■ Stop' : 'Listen';
liveListenEventBtn.classList.toggle('active', active);
}
}

function stopLiveListen() {
if (liveListenWS) {
liveListenWS.onStop(() => {});
liveListenWS.stop();
liveListenWS = null;
}
liveListenTarget = null;
setLiveListenUI(false);
}

function startLiveListen(freq, bw, detectedMode) {
if (!Number.isFinite(freq)) return;
const mode = resolveListenMode(detectedMode);
const width = Number.isFinite(bw) && bw > 0 ? bw : 12000;

// Stop any old HTTP audio
if (liveAudio) { liveAudio.pause(); liveAudio = null; }

// Switch on the fly if already listening
if (liveListenWS) {
liveListenWS.onStop(() => {});
liveListenWS.stop();
liveListenWS = null;
}

liveListenTarget = { freq, bw: width, mode };

liveListenWS = new LiveListenWS(freq, width, mode);
liveListenWS.onStop(() => {
liveListenWS = null;
liveListenTarget = null;
setLiveListenUI(false);
});
liveListenWS.start();
setLiveListenUI(true);
}

function matchesListenTarget(signal) {
if (!liveListenTarget || !signal) return false;
const bw = signal.bw_hz || liveListenTarget.bw || 0;
const tol = Math.max(500, bw * 0.5);
return Math.abs((signal.center_hz || 0) - liveListenTarget.freq) <= tol;
}

let gpuInfo = { available: false, active: false, error: '' };

let zoom = 1;
@@ -934,6 +997,12 @@ function renderSpectrum() {
ctx.fillRect(x1, 10, boxW, h - 28);
ctx.strokeRect(x1, 10, boxW, h - 28);

if (matchesListenTarget(s)) {
ctx.strokeStyle = 'rgba(255, 92, 92, 0.95)';
ctx.lineWidth = 2.5;
ctx.strokeRect(x1 - 1, 9, boxW + 2, h - 26);
}

// Label badges with dark background for readability
const labelX = Math.max(4, x1 + 4);
const baseY = 14;
@@ -1216,6 +1285,7 @@ function _createSignalItem(s) {
btn.style.borderLeftColor = mc.label;
btn.style.borderLeftWidth = '3px';
btn.style.borderLeftStyle = 'solid';
if (matchesListenTarget(s)) btn.classList.add('listening');
return btn;
}

@@ -1245,6 +1315,7 @@ function _patchSignalItem(el, s) {
el.dataset.bw = s.bw_hz || 0;
el.dataset.class = mod;
el.style.borderLeftColor = mc.label;
el.classList.toggle('listening', matchesListenTarget(s));
}

function renderLists() {
@@ -1607,7 +1678,17 @@ function handleSpectrumClick(ev) {
for (let i = liveSignalRects.length - 1; i >= 0; i--) {
const r = liveSignalRects[i];
if (x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h) {
tuneToFrequency(r.signal.center_hz);
const sig = r.signal;
const freq = sig.center_hz;
const bw = sig.bw_hz || 12000;
const mode = sig.class?.mod_type || '';
startLiveListen(freq, bw, mode);
window._selectedSignal = { freq, bw, mode };
// Update selected signal in list
signalList.querySelectorAll('.signal-item').forEach(el => {
const elFreq = parseFloat(el.dataset.center || '0');
el.classList.toggle('active', Math.abs(elFreq - freq) < Math.max(500, bw * 0.5));
});
return;
}
}
@@ -1896,30 +1977,14 @@ if (liveListenEventBtn) {

// Toggle off if already listening
if (liveListenWS && liveListenWS.playing) {
liveListenWS.stop();
liveListenWS = null;
liveListenEventBtn.textContent = 'Listen';
liveListenEventBtn.classList.remove('active');
if (liveListenBtn) { liveListenBtn.textContent = 'Live Listen'; liveListenBtn.classList.remove('active'); }
stopLiveListen();
return;
}

const freq = ev.center_hz;
const bw = ev.bandwidth_hz || 12000;
const mode = (listenModeSelect?.value || ev.class?.mod_type || 'NFM');

if (liveAudio) { liveAudio.pause(); liveAudio = null; }

liveListenWS = new LiveListenWS(freq, bw, mode);
liveListenWS.onStop(() => {
liveListenEventBtn.textContent = 'Listen';
liveListenEventBtn.classList.remove('active');
if (liveListenBtn) { liveListenBtn.textContent = 'Live Listen'; liveListenBtn.classList.remove('active'); }
liveListenWS = null;
});
liveListenWS.start();
liveListenEventBtn.textContent = '■ Stop';
liveListenEventBtn.classList.add('active');
const mode = ev.class?.mod_type || 'NFM';
startLiveListen(freq, bw, mode);
});
}
if (decodeEventBtn) {
@@ -1980,10 +2045,7 @@ if (liveListenBtn) {
liveListenBtn.addEventListener('click', async () => {
// Toggle: if already listening, stop
if (liveListenWS && liveListenWS.playing) {
liveListenWS.stop();
liveListenWS = null;
liveListenBtn.textContent = 'Live Listen';
liveListenBtn.classList.remove('active');
stopLiveListen();
return;
}

@@ -2001,20 +2063,8 @@ if (liveListenBtn) {
mode = first.dataset.class || '';
}
if (!Number.isFinite(freq)) return;
mode = (listenModeSelect?.value === 'Auto' || listenModeSelect?.value === '') ? (mode || 'NFM') : listenModeSelect.value;

// Stop any old HTTP audio
if (liveAudio) { liveAudio.pause(); liveAudio = null; }

liveListenWS = new LiveListenWS(freq, bw, mode);
liveListenWS.onStop(() => {
liveListenBtn.textContent = 'Live Listen';
liveListenBtn.classList.remove('active');
liveListenWS = null;
});
liveListenWS.start();
liveListenBtn.textContent = '■ Stop';
liveListenBtn.classList.add('active');
startLiveListen(freq, bw, mode);
});
}



+ 1
- 0
web/style.css Прегледај датотеку

@@ -362,6 +362,7 @@ input[type="range"]::-moz-range-thumb {
background: var(--panel-2); cursor: pointer; transition: border-color 0.12s;
}
.list-item:hover, .list-item.active { border-color: rgba(0, 255, 200, 0.28); }
.list-item.listening { border-color: rgba(255, 92, 92, 0.55); box-shadow: 0 0 0 1px rgba(255, 92, 92, 0.35) inset; }
.item-top, .item-bottom { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.item-top { margin-bottom: 3px; }
.item-title { font-family: var(--mono); font-size: 0.82rem; font-weight: 700; color: #e7f1ff; }


Loading…
Откажи
Сачувај