mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
chore: commit all pending changes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -423,9 +423,16 @@
|
||||
let alertsEnabled = true;
|
||||
let detectionSoundEnabled = localStorage.getItem('adsb_detectionSound') !== 'false'; // Default on
|
||||
let soundedAircraft = {}; // Track aircraft we've played detection sound for
|
||||
const MAP_CROSSHAIR_DURATION_MS = 620;
|
||||
const MAP_CROSSHAIR_DURATION_MS = 1500;
|
||||
const PANEL_SELECTION_BASE_ZOOM = 10;
|
||||
const PANEL_SELECTION_MAX_ZOOM = 12;
|
||||
const PANEL_SELECTION_ZOOM_INCREMENT = 1.4;
|
||||
const PANEL_SELECTION_STAGE1_DURATION_SEC = 1.05;
|
||||
const PANEL_SELECTION_STAGE2_DURATION_SEC = 1.15;
|
||||
const PANEL_SELECTION_STAGE_GAP_MS = 180;
|
||||
let mapCrosshairResetTimer = null;
|
||||
let mapCrosshairFallbackTimer = null;
|
||||
let panelSelectionFallbackTimer = null;
|
||||
let panelSelectionStageTimer = null;
|
||||
let mapCrosshairRequestId = 0;
|
||||
// Watchlist - persisted to localStorage
|
||||
let watchlist = JSON.parse(localStorage.getItem('adsb_watchlist') || '[]');
|
||||
@@ -2792,18 +2799,32 @@ sudo make install</code>
|
||||
`;
|
||||
}
|
||||
|
||||
function triggerMapCrosshairAnimation(lat, lon) {
|
||||
function triggerMapCrosshairAnimation(lat, lon, durationMs = MAP_CROSSHAIR_DURATION_MS, lockToMapCenter = false) {
|
||||
if (!radarMap) return;
|
||||
const overlay = document.getElementById('mapCrosshairOverlay');
|
||||
if (!overlay) return;
|
||||
|
||||
const point = radarMap.latLngToContainerPoint([lat, lon]);
|
||||
const size = radarMap.getSize();
|
||||
const targetX = Math.max(0, Math.min(size.x, point.x));
|
||||
const targetY = Math.max(0, Math.min(size.y, point.y));
|
||||
let targetX;
|
||||
let targetY;
|
||||
|
||||
overlay.style.setProperty('--target-x', `${targetX}px`);
|
||||
overlay.style.setProperty('--target-y', `${targetY}px`);
|
||||
if (lockToMapCenter) {
|
||||
targetX = size.x / 2;
|
||||
targetY = size.y / 2;
|
||||
} else {
|
||||
const point = radarMap.latLngToContainerPoint([lat, lon]);
|
||||
targetX = Math.max(0, Math.min(size.x, point.x));
|
||||
targetY = Math.max(0, Math.min(size.y, point.y));
|
||||
}
|
||||
|
||||
const startX = size.x + 8;
|
||||
const startY = size.y + 8;
|
||||
|
||||
overlay.style.setProperty('--crosshair-x-start', `${startX}px`);
|
||||
overlay.style.setProperty('--crosshair-y-start', `${startY}px`);
|
||||
overlay.style.setProperty('--crosshair-x-end', `${targetX}px`);
|
||||
overlay.style.setProperty('--crosshair-y-end', `${targetY}px`);
|
||||
overlay.style.setProperty('--crosshair-duration', `${durationMs}ms`);
|
||||
overlay.classList.remove('active');
|
||||
void overlay.offsetWidth;
|
||||
overlay.classList.add('active');
|
||||
@@ -2814,16 +2835,102 @@ sudo make install</code>
|
||||
mapCrosshairResetTimer = setTimeout(() => {
|
||||
overlay.classList.remove('active');
|
||||
mapCrosshairResetTimer = null;
|
||||
}, MAP_CROSSHAIR_DURATION_MS + 40);
|
||||
}, durationMs + 100);
|
||||
}
|
||||
|
||||
function getPanelSelectionFinalZoom() {
|
||||
if (!radarMap) return PANEL_SELECTION_BASE_ZOOM;
|
||||
const currentZoom = radarMap.getZoom();
|
||||
const maxZoom = typeof radarMap.getMaxZoom === 'function' ? radarMap.getMaxZoom() : PANEL_SELECTION_MAX_ZOOM;
|
||||
return Math.min(
|
||||
PANEL_SELECTION_MAX_ZOOM,
|
||||
maxZoom,
|
||||
Math.max(PANEL_SELECTION_BASE_ZOOM, currentZoom + PANEL_SELECTION_ZOOM_INCREMENT)
|
||||
);
|
||||
}
|
||||
|
||||
function getPanelSelectionIntermediateZoom(finalZoom) {
|
||||
if (!radarMap) return finalZoom;
|
||||
const currentZoom = radarMap.getZoom();
|
||||
if (finalZoom - currentZoom < 0.8) {
|
||||
return finalZoom;
|
||||
}
|
||||
const midpointZoom = currentZoom + ((finalZoom - currentZoom) * 0.55);
|
||||
return Math.min(finalZoom - 0.45, midpointZoom);
|
||||
}
|
||||
|
||||
function runPanelSelectionAnimation(lat, lon, requestId) {
|
||||
if (!radarMap) return;
|
||||
|
||||
const finalZoom = getPanelSelectionFinalZoom();
|
||||
const intermediateZoom = getPanelSelectionIntermediateZoom(finalZoom);
|
||||
const sequenceDurationMs = Math.round(
|
||||
((PANEL_SELECTION_STAGE1_DURATION_SEC + PANEL_SELECTION_STAGE2_DURATION_SEC) * 1000) +
|
||||
PANEL_SELECTION_STAGE_GAP_MS + 260
|
||||
);
|
||||
const startSecondStage = () => {
|
||||
if (requestId !== mapCrosshairRequestId) return;
|
||||
radarMap.flyTo([lat, lon], finalZoom, {
|
||||
animate: true,
|
||||
duration: PANEL_SELECTION_STAGE2_DURATION_SEC,
|
||||
easeLinearity: 0.2
|
||||
});
|
||||
};
|
||||
|
||||
triggerMapCrosshairAnimation(
|
||||
lat,
|
||||
lon,
|
||||
Math.max(MAP_CROSSHAIR_DURATION_MS, sequenceDurationMs),
|
||||
true
|
||||
);
|
||||
|
||||
if (intermediateZoom >= finalZoom - 0.1) {
|
||||
radarMap.flyTo([lat, lon], finalZoom, {
|
||||
animate: true,
|
||||
duration: PANEL_SELECTION_STAGE2_DURATION_SEC,
|
||||
easeLinearity: 0.2
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let stage1Handled = false;
|
||||
const finishStage1 = () => {
|
||||
if (stage1Handled || requestId !== mapCrosshairRequestId) return;
|
||||
stage1Handled = true;
|
||||
if (panelSelectionFallbackTimer) {
|
||||
clearTimeout(panelSelectionFallbackTimer);
|
||||
panelSelectionFallbackTimer = null;
|
||||
}
|
||||
panelSelectionStageTimer = setTimeout(() => {
|
||||
panelSelectionStageTimer = null;
|
||||
startSecondStage();
|
||||
}, PANEL_SELECTION_STAGE_GAP_MS);
|
||||
};
|
||||
|
||||
radarMap.once('moveend', finishStage1);
|
||||
panelSelectionFallbackTimer = setTimeout(
|
||||
finishStage1,
|
||||
Math.round(PANEL_SELECTION_STAGE1_DURATION_SEC * 1000) + 160
|
||||
);
|
||||
|
||||
radarMap.flyTo([lat, lon], intermediateZoom, {
|
||||
animate: true,
|
||||
duration: PANEL_SELECTION_STAGE1_DURATION_SEC,
|
||||
easeLinearity: 0.2
|
||||
});
|
||||
}
|
||||
|
||||
function selectAircraft(icao, source = 'map') {
|
||||
const prevSelected = selectedIcao;
|
||||
selectedIcao = icao;
|
||||
mapCrosshairRequestId += 1;
|
||||
if (mapCrosshairFallbackTimer) {
|
||||
clearTimeout(mapCrosshairFallbackTimer);
|
||||
mapCrosshairFallbackTimer = null;
|
||||
if (panelSelectionFallbackTimer) {
|
||||
clearTimeout(panelSelectionFallbackTimer);
|
||||
panelSelectionFallbackTimer = null;
|
||||
}
|
||||
if (panelSelectionStageTimer) {
|
||||
clearTimeout(panelSelectionStageTimer);
|
||||
panelSelectionStageTimer = null;
|
||||
}
|
||||
|
||||
// Update marker icons for both previous and new selection
|
||||
@@ -2853,19 +2960,8 @@ sudo make install</code>
|
||||
const targetLon = ac.lon;
|
||||
|
||||
if (source === 'panel' && radarMap) {
|
||||
const requestId = mapCrosshairRequestId;
|
||||
let crosshairTriggered = false;
|
||||
const runCrosshair = () => {
|
||||
if (crosshairTriggered || requestId !== mapCrosshairRequestId) return;
|
||||
crosshairTriggered = true;
|
||||
if (mapCrosshairFallbackTimer) {
|
||||
clearTimeout(mapCrosshairFallbackTimer);
|
||||
mapCrosshairFallbackTimer = null;
|
||||
}
|
||||
triggerMapCrosshairAnimation(targetLat, targetLon);
|
||||
};
|
||||
radarMap.once('moveend', runCrosshair);
|
||||
mapCrosshairFallbackTimer = setTimeout(runCrosshair, 450);
|
||||
runPanelSelectionAnimation(targetLat, targetLon, mapCrosshairRequestId);
|
||||
return;
|
||||
}
|
||||
|
||||
radarMap.setView([targetLat, targetLon], 10);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -2193,7 +2193,7 @@
|
||||
<div id="sstvScopePanel" style="display: none; margin-bottom: 12px;">
|
||||
<div style="background: #0a0a0a; border: 1px solid #1e1a2e; border-radius: 6px; padding: 8px 10px; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px;">
|
||||
<span>Signal Scope</span>
|
||||
<span>Audio Waveform</span>
|
||||
<div style="display: flex; gap: 14px;">
|
||||
<span>RMS: <span id="sstvScopeRmsLabel" style="color: #c080ff; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
<span>PEAK: <span id="sstvScopePeakLabel" style="color: #f44; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
@@ -2461,7 +2461,7 @@
|
||||
<div id="sstvGeneralScopePanel" style="display: none; margin-bottom: 12px;">
|
||||
<div style="background: #0a0a0a; border: 1px solid #1e1a2e; border-radius: 6px; padding: 8px 10px; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px;">
|
||||
<span>Signal Scope</span>
|
||||
<span>Audio Waveform</span>
|
||||
<div style="display: flex; gap: 14px;">
|
||||
<span>RMS: <span id="sstvGeneralScopeRmsLabel" style="color: #c080ff; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
<span>PEAK: <span id="sstvGeneralScopePeakLabel" style="color: #f44; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
@@ -2835,7 +2835,7 @@
|
||||
<div id="pagerScopePanel" style="display: none; margin-bottom: 12px;">
|
||||
<div style="background: #0a0a0a; border: 1px solid #1a1a2e; border-radius: 6px; padding: 8px 10px; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px;">
|
||||
<span>Signal Scope</span>
|
||||
<span>Audio Waveform</span>
|
||||
<div style="display: flex; gap: 14px;">
|
||||
<span>RMS: <span id="scopeRmsLabel" style="color: #0ff; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
<span>PEAK: <span id="scopePeakLabel" style="color: #f44; font-variant-numeric: tabular-nums;">0</span></span>
|
||||
@@ -2853,7 +2853,7 @@
|
||||
<div id="sensorScopePanel" style="display: none; margin-bottom: 12px;">
|
||||
<div style="background: #0a0a0a; border: 1px solid #1a2e1a; border-radius: 6px; padding: 8px 10px; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 1px;">
|
||||
<span>Signal Scope</span>
|
||||
<span>Audio Waveform</span>
|
||||
<div style="display: flex; gap: 14px;">
|
||||
<span>RSSI: <span id="sensorScopeRssiLabel" style="color: #0f0; font-variant-numeric: tabular-nums;">--</span><span style="color: #444;"> dB</span></span>
|
||||
<span>SNR: <span id="sensorScopeSnrLabel" style="color: #fa0; font-variant-numeric: tabular-nums;">--</span><span style="color: #444;"> dB</span></span>
|
||||
@@ -3934,61 +3934,157 @@
|
||||
let sensorScopeCtx = null;
|
||||
let sensorScopeAnim = null;
|
||||
let sensorScopeHistory = [];
|
||||
let sensorScopeWaveBuffer = [];
|
||||
let sensorScopeDisplayWave = [];
|
||||
const SENSOR_SCOPE_LEN = 200;
|
||||
const SENSOR_SCOPE_WAVE_BUFFER_LEN = 2048;
|
||||
const SENSOR_SCOPE_WAVE_INPUT_SMOOTH_ALPHA = 0.55;
|
||||
const SENSOR_SCOPE_WAVE_DISPLAY_SMOOTH_ALPHA = 0.22;
|
||||
const SENSOR_SCOPE_WAVE_IDLE_DECAY = 0.96;
|
||||
let sensorScopeRssi = 0;
|
||||
let sensorScopeSnr = 0;
|
||||
let sensorScopeTargetRssi = 0;
|
||||
let sensorScopeTargetSnr = 0;
|
||||
let sensorScopeMsgBurst = 0;
|
||||
let sensorScopeLastPulse = 0;
|
||||
let sensorScopeLastWaveAt = 0;
|
||||
let sensorScopeLastInputSample = 0;
|
||||
|
||||
function resizeSensorScopeCanvas(canvas) {
|
||||
if (!canvas) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = Math.max(1, Math.floor(rect.width * dpr));
|
||||
const height = Math.max(1, Math.floor(rect.height * dpr));
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
function buildSensorWaveformFallback(rssi, snr, noise, points = 160) {
|
||||
const rssiNorm = Math.min(Math.max(Math.abs(rssi) / 40, 0), 1);
|
||||
const snrNorm = Math.min(Math.max((snr + 5) / 35, 0), 1);
|
||||
const noiseNorm = Math.min(Math.max(Math.abs(noise) / 40, 0), 1);
|
||||
|
||||
const amplitude = Math.max(0.06, Math.min(1.0, (0.6 * rssiNorm + 0.4 * snrNorm) - (0.22 * noiseNorm)));
|
||||
const cycles = 3 + (snrNorm * 8);
|
||||
const harmonic = 0.25 + (0.35 * snrNorm);
|
||||
const hiss = 0.08 + (0.18 * noiseNorm);
|
||||
const phase = (performance.now() * 0.002 * (1.4 + (snrNorm * 2.2))) % (Math.PI * 2);
|
||||
|
||||
const waveform = [];
|
||||
for (let i = 0; i < points; i++) {
|
||||
const t = points > 1 ? (i / (points - 1)) : 0;
|
||||
const base = Math.sin((Math.PI * 2 * cycles * t) + phase);
|
||||
const overtone = Math.sin((Math.PI * 2 * (cycles * 2.4) * t) + (phase * 0.7));
|
||||
const noiseWobble = Math.sin((Math.PI * 2 * (cycles * 7.0) * t) + (phase * 2.1));
|
||||
let sample = amplitude * (base + (harmonic * overtone) + (hiss * noiseWobble));
|
||||
sample /= (1 + harmonic + hiss);
|
||||
waveform.push(Math.round(Math.max(-1, Math.min(1, sample)) * 127));
|
||||
}
|
||||
return waveform;
|
||||
}
|
||||
|
||||
function appendSensorWaveformSamples(waveform) {
|
||||
if (!Array.isArray(waveform) || waveform.length === 0) return;
|
||||
|
||||
for (const packedSample of waveform) {
|
||||
const sample = Number(packedSample);
|
||||
if (!Number.isFinite(sample)) continue;
|
||||
const normalized = Math.max(-127, Math.min(127, sample)) / 127;
|
||||
sensorScopeLastInputSample += (normalized - sensorScopeLastInputSample) * SENSOR_SCOPE_WAVE_INPUT_SMOOTH_ALPHA;
|
||||
sensorScopeWaveBuffer.push(sensorScopeLastInputSample);
|
||||
}
|
||||
|
||||
if (sensorScopeWaveBuffer.length > SENSOR_SCOPE_WAVE_BUFFER_LEN) {
|
||||
sensorScopeWaveBuffer.splice(0, sensorScopeWaveBuffer.length - SENSOR_SCOPE_WAVE_BUFFER_LEN);
|
||||
}
|
||||
sensorScopeLastWaveAt = performance.now();
|
||||
}
|
||||
|
||||
function applySensorScopeData(scopeData) {
|
||||
if (!scopeData || typeof scopeData !== 'object') return;
|
||||
|
||||
const parsedRssi = Number(scopeData.rssi);
|
||||
const parsedSnr = Number(scopeData.snr);
|
||||
const parsedNoise = Number(scopeData.noise);
|
||||
|
||||
const rssi = Number.isFinite(parsedRssi) ? parsedRssi : 0;
|
||||
const snr = Number.isFinite(parsedSnr) ? parsedSnr : 0;
|
||||
const noise = Number.isFinite(parsedNoise) ? parsedNoise : 0;
|
||||
|
||||
sensorScopeTargetRssi = rssi;
|
||||
sensorScopeTargetSnr = snr;
|
||||
|
||||
if (Array.isArray(scopeData.waveform) && scopeData.waveform.length) {
|
||||
appendSensorWaveformSamples(scopeData.waveform);
|
||||
} else {
|
||||
appendSensorWaveformSamples(buildSensorWaveformFallback(rssi, snr, noise));
|
||||
}
|
||||
}
|
||||
|
||||
function initSensorScope() {
|
||||
const canvas = document.getElementById('sensorScopeCanvas');
|
||||
if (!canvas) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * (window.devicePixelRatio || 1);
|
||||
canvas.height = rect.height * (window.devicePixelRatio || 1);
|
||||
|
||||
if (sensorScopeAnim) {
|
||||
cancelAnimationFrame(sensorScopeAnim);
|
||||
sensorScopeAnim = null;
|
||||
}
|
||||
|
||||
resizeSensorScopeCanvas(canvas);
|
||||
sensorScopeCtx = canvas.getContext('2d');
|
||||
sensorScopeHistory = new Array(SENSOR_SCOPE_LEN).fill(0);
|
||||
sensorScopeWaveBuffer = [];
|
||||
sensorScopeDisplayWave = [];
|
||||
sensorScopeRssi = 0;
|
||||
sensorScopeSnr = 0;
|
||||
sensorScopeTargetRssi = 0;
|
||||
sensorScopeTargetSnr = 0;
|
||||
sensorScopeMsgBurst = 0;
|
||||
sensorScopeLastPulse = 0;
|
||||
sensorScopeLastWaveAt = 0;
|
||||
sensorScopeLastInputSample = 0;
|
||||
drawSensorScope();
|
||||
}
|
||||
|
||||
function drawSensorScope() {
|
||||
const ctx = sensorScopeCtx;
|
||||
if (!ctx) return;
|
||||
|
||||
resizeSensorScopeCanvas(ctx.canvas);
|
||||
const W = ctx.canvas.width;
|
||||
const H = ctx.canvas.height;
|
||||
const midY = H / 2;
|
||||
|
||||
// Phosphor persistence
|
||||
ctx.fillStyle = 'rgba(5, 5, 16, 0.3)';
|
||||
ctx.fillStyle = 'rgba(5, 5, 16, 0.26)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// Smooth towards targets (decay when no new packets)
|
||||
// Smooth towards targets
|
||||
sensorScopeRssi += (sensorScopeTargetRssi - sensorScopeRssi) * 0.25;
|
||||
sensorScopeSnr += (sensorScopeTargetSnr - sensorScopeSnr) * 0.15;
|
||||
|
||||
// Decay targets back to zero between packets
|
||||
// Decay targets back to idle between updates
|
||||
sensorScopeTargetRssi *= 0.97;
|
||||
sensorScopeTargetSnr *= 0.97;
|
||||
|
||||
// RSSI is typically negative dBm (e.g. -0.1 to -30+)
|
||||
// Normalize: map absolute RSSI to 0-1 range (0 dB = max, -40 dB = min)
|
||||
// Keep amplitude envelope for context
|
||||
const rssiNorm = Math.min(Math.max(Math.abs(sensorScopeRssi) / 40, 0), 1.0);
|
||||
sensorScopeHistory.push(rssiNorm);
|
||||
if (sensorScopeHistory.length > SENSOR_SCOPE_LEN) {
|
||||
sensorScopeHistory.shift();
|
||||
}
|
||||
|
||||
// Grid lines
|
||||
// Grid lines (horizontal + vertical)
|
||||
ctx.strokeStyle = 'rgba(40, 80, 40, 0.4)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineWidth = 0.8;
|
||||
for (let i = 1; i < 8; i++) {
|
||||
const gx = (W / 8) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(gx, 0);
|
||||
ctx.lineTo(gx, H);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let g = 0.25; g < 1; g += 0.25) {
|
||||
const gy = midY - g * midY;
|
||||
const gy2 = midY + g * midY;
|
||||
@@ -4000,40 +4096,92 @@
|
||||
|
||||
// Center baseline
|
||||
ctx.strokeStyle = 'rgba(60, 100, 60, 0.5)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, midY);
|
||||
ctx.lineTo(W, midY);
|
||||
ctx.stroke();
|
||||
|
||||
// Waveform (mirrored, green theme for 433)
|
||||
const stepX = W / SENSOR_SCOPE_LEN;
|
||||
ctx.strokeStyle = '#0f0';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.shadowColor = '#0f0';
|
||||
ctx.shadowBlur = 4;
|
||||
|
||||
// Upper half
|
||||
// Slow envelope as context around baseline
|
||||
const envStepX = W / (SENSOR_SCOPE_LEN - 1);
|
||||
ctx.strokeStyle = 'rgba(86, 230, 120, 0.45)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < sensorScopeHistory.length; i++) {
|
||||
const x = i * stepX;
|
||||
const amp = sensorScopeHistory[i] * midY * 0.9;
|
||||
const x = i * envStepX;
|
||||
const amp = sensorScopeHistory[i] * midY * 0.85;
|
||||
const y = midY - amp;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Lower half (mirror)
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < sensorScopeHistory.length; i++) {
|
||||
const x = i * stepX;
|
||||
const amp = sensorScopeHistory[i] * midY * 0.9;
|
||||
const x = i * envStepX;
|
||||
const amp = sensorScopeHistory[i] * midY * 0.85;
|
||||
const y = midY + amp;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Actual waveform trace
|
||||
const waveformPointCount = Math.min(Math.max(120, Math.floor(W / 3.2)), 420);
|
||||
if (sensorScopeWaveBuffer.length > 1) {
|
||||
const waveIsFresh = (performance.now() - sensorScopeLastWaveAt) < 1000;
|
||||
const sourceLen = sensorScopeWaveBuffer.length;
|
||||
const sourceWindow = Math.min(sourceLen, 1536);
|
||||
const sourceStart = sourceLen - sourceWindow;
|
||||
|
||||
if (sensorScopeDisplayWave.length !== waveformPointCount) {
|
||||
sensorScopeDisplayWave = new Array(waveformPointCount).fill(0);
|
||||
}
|
||||
|
||||
for (let i = 0; i < waveformPointCount; i++) {
|
||||
const a = sourceStart + Math.floor((i / waveformPointCount) * sourceWindow);
|
||||
const b = sourceStart + Math.floor(((i + 1) / waveformPointCount) * sourceWindow);
|
||||
const start = Math.max(sourceStart, Math.min(sourceLen - 1, a));
|
||||
const end = Math.max(start + 1, Math.min(sourceLen, b));
|
||||
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = start; j < end; j++) {
|
||||
sum += sensorScopeWaveBuffer[j];
|
||||
count++;
|
||||
}
|
||||
const targetSample = count > 0 ? (sum / count) : 0;
|
||||
sensorScopeDisplayWave[i] += (targetSample - sensorScopeDisplayWave[i]) * SENSOR_SCOPE_WAVE_DISPLAY_SMOOTH_ALPHA;
|
||||
}
|
||||
|
||||
ctx.strokeStyle = waveIsFresh ? '#40ff7a' : 'rgba(64, 255, 122, 0.45)';
|
||||
ctx.lineWidth = 1.7;
|
||||
ctx.shadowColor = '#40ff7a';
|
||||
ctx.shadowBlur = waveIsFresh ? 6 : 2;
|
||||
|
||||
const stepX = waveformPointCount > 1 ? (W / (waveformPointCount - 1)) : W;
|
||||
ctx.beginPath();
|
||||
const firstY = midY - (sensorScopeDisplayWave[0] * midY * 0.9);
|
||||
ctx.moveTo(0, firstY);
|
||||
for (let i = 1; i < waveformPointCount - 1; i++) {
|
||||
const x = i * stepX;
|
||||
const y = midY - (sensorScopeDisplayWave[i] * midY * 0.9);
|
||||
const nx = (i + 1) * stepX;
|
||||
const ny = midY - (sensorScopeDisplayWave[i + 1] * midY * 0.9);
|
||||
const cx = (x + nx) / 2;
|
||||
const cy = (y + ny) / 2;
|
||||
ctx.quadraticCurveTo(x, y, cx, cy);
|
||||
}
|
||||
const lastX = (waveformPointCount - 1) * stepX;
|
||||
const lastY = midY - (sensorScopeDisplayWave[waveformPointCount - 1] * midY * 0.9);
|
||||
ctx.lineTo(lastX, lastY);
|
||||
ctx.stroke();
|
||||
|
||||
if (!waveIsFresh) {
|
||||
for (let i = 0; i < sensorScopeDisplayWave.length; i++) {
|
||||
sensorScopeDisplayWave[i] *= SENSOR_SCOPE_WAVE_IDLE_DECAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// SNR indicator (amber dashed line)
|
||||
@@ -4050,7 +4198,7 @@
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
// Sensor decode flash (green overlay)
|
||||
// Sensor decode flash
|
||||
if (sensorScopeMsgBurst > 0.01) {
|
||||
ctx.fillStyle = `rgba(0, 255, 100, ${sensorScopeMsgBurst * 0.15})`;
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
@@ -4064,11 +4212,15 @@
|
||||
if (rssiLabel) rssiLabel.textContent = sensorScopeRssi < -0.5 ? sensorScopeRssi.toFixed(1) : '--';
|
||||
if (snrLabel) snrLabel.textContent = sensorScopeSnr > 0.5 ? sensorScopeSnr.toFixed(1) : '--';
|
||||
if (statusLabel) {
|
||||
if (Math.abs(sensorScopeRssi) > 1) {
|
||||
statusLabel.textContent = 'SIGNAL';
|
||||
statusLabel.style.color = '#0f0';
|
||||
const waveIsFresh = (performance.now() - sensorScopeLastWaveAt) < 1000;
|
||||
if (Math.abs(sensorScopeRssi) > 1.2 && waveIsFresh) {
|
||||
statusLabel.textContent = 'DEMODULATING';
|
||||
statusLabel.style.color = '#40ff7a';
|
||||
} else if (Math.abs(sensorScopeRssi) > 0.6) {
|
||||
statusLabel.textContent = 'CARRIER';
|
||||
statusLabel.style.color = '#78ff9a';
|
||||
} else {
|
||||
statusLabel.textContent = 'MONITORING';
|
||||
statusLabel.textContent = 'QUIET';
|
||||
statusLabel.style.color = '#555';
|
||||
}
|
||||
}
|
||||
@@ -4082,6 +4234,11 @@
|
||||
sensorScopeAnim = null;
|
||||
}
|
||||
sensorScopeCtx = null;
|
||||
sensorScopeWaveBuffer = [];
|
||||
sensorScopeDisplayWave = [];
|
||||
sensorScopeHistory = [];
|
||||
sensorScopeLastWaveAt = 0;
|
||||
sensorScopeLastInputSample = 0;
|
||||
}
|
||||
|
||||
// Start sensor decoding
|
||||
@@ -4256,6 +4413,11 @@
|
||||
const placeholder = output.querySelector('.placeholder');
|
||||
if (placeholder) placeholder.style.display = 'none';
|
||||
|
||||
// Agent polling may only return decoded packets, so derive scope updates from packet levels.
|
||||
if (msg && (msg.rssi !== undefined || msg.snr !== undefined || msg.noise !== undefined)) {
|
||||
applySensorScopeData(msg);
|
||||
}
|
||||
|
||||
// Create signal card if SignalCards is available
|
||||
if (typeof SignalCards !== 'undefined' && SignalCards.createFromSensor) {
|
||||
const card = SignalCards.createFromSensor(msg);
|
||||
@@ -4303,8 +4465,7 @@
|
||||
if (data.type === 'sensor') {
|
||||
addSensorReading(data);
|
||||
} else if (data.type === 'scope') {
|
||||
sensorScopeTargetRssi = data.rssi;
|
||||
sensorScopeTargetSnr = data.snr;
|
||||
applySensorScopeData(data);
|
||||
} else if (data.type === 'status') {
|
||||
if (data.text === 'stopped') {
|
||||
setSensorRunning(false);
|
||||
@@ -4332,6 +4493,12 @@
|
||||
// Flash sensor scope green on decode
|
||||
sensorScopeMsgBurst = 1.0;
|
||||
|
||||
// Fallback when no dedicated scope packet has arrived recently.
|
||||
if ((data.rssi !== undefined || data.snr !== undefined || data.noise !== undefined)
|
||||
&& ((performance.now() - sensorScopeLastWaveAt) > 250)) {
|
||||
applySensorScopeData(data);
|
||||
}
|
||||
|
||||
sensorCount++;
|
||||
document.getElementById('sensorCount').textContent = sensorCount;
|
||||
|
||||
@@ -5208,54 +5375,111 @@
|
||||
let pagerScopeCtx = null;
|
||||
let pagerScopeAnim = null;
|
||||
let pagerScopeHistory = [];
|
||||
let pagerScopeWaveBuffer = [];
|
||||
let pagerScopeDisplayWave = [];
|
||||
const SCOPE_HISTORY_LEN = 200;
|
||||
const SCOPE_WAVE_BUFFER_LEN = 2048;
|
||||
const SCOPE_WAVE_INPUT_SMOOTH_ALPHA = 0.55;
|
||||
const SCOPE_WAVE_DISPLAY_SMOOTH_ALPHA = 0.22;
|
||||
const SCOPE_WAVE_IDLE_DECAY = 0.96;
|
||||
let pagerScopeRms = 0;
|
||||
let pagerScopePeak = 0;
|
||||
let pagerScopeTargetRms = 0;
|
||||
let pagerScopeTargetPeak = 0;
|
||||
let pagerScopeMsgBurst = 0;
|
||||
let pagerScopeLastWaveAt = 0;
|
||||
let pagerScopeLastInputSample = 0;
|
||||
|
||||
function resizePagerScopeCanvas(canvas) {
|
||||
if (!canvas) return;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = Math.max(1, Math.floor(rect.width * dpr));
|
||||
const height = Math.max(1, Math.floor(rect.height * dpr));
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
function applyPagerScopeData(scopeData) {
|
||||
if (!scopeData || typeof scopeData !== 'object') return;
|
||||
|
||||
pagerScopeTargetRms = Number(scopeData.rms) || 0;
|
||||
pagerScopeTargetPeak = Number(scopeData.peak) || 0;
|
||||
|
||||
if (Array.isArray(scopeData.waveform) && scopeData.waveform.length) {
|
||||
for (const packedSample of scopeData.waveform) {
|
||||
const sample = Number(packedSample);
|
||||
if (!Number.isFinite(sample)) continue;
|
||||
const normalized = Math.max(-127, Math.min(127, sample)) / 127;
|
||||
pagerScopeLastInputSample += (normalized - pagerScopeLastInputSample) * SCOPE_WAVE_INPUT_SMOOTH_ALPHA;
|
||||
pagerScopeWaveBuffer.push(pagerScopeLastInputSample);
|
||||
}
|
||||
if (pagerScopeWaveBuffer.length > SCOPE_WAVE_BUFFER_LEN) {
|
||||
pagerScopeWaveBuffer.splice(0, pagerScopeWaveBuffer.length - SCOPE_WAVE_BUFFER_LEN);
|
||||
}
|
||||
pagerScopeLastWaveAt = performance.now();
|
||||
}
|
||||
}
|
||||
|
||||
function initPagerScope() {
|
||||
const canvas = document.getElementById('pagerScopeCanvas');
|
||||
if (!canvas) return;
|
||||
// Set actual pixel resolution
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * (window.devicePixelRatio || 1);
|
||||
canvas.height = rect.height * (window.devicePixelRatio || 1);
|
||||
|
||||
if (pagerScopeAnim) {
|
||||
cancelAnimationFrame(pagerScopeAnim);
|
||||
pagerScopeAnim = null;
|
||||
}
|
||||
|
||||
resizePagerScopeCanvas(canvas);
|
||||
pagerScopeCtx = canvas.getContext('2d');
|
||||
pagerScopeHistory = new Array(SCOPE_HISTORY_LEN).fill(0);
|
||||
pagerScopeWaveBuffer = [];
|
||||
pagerScopeDisplayWave = [];
|
||||
pagerScopeRms = 0;
|
||||
pagerScopePeak = 0;
|
||||
pagerScopeTargetRms = 0;
|
||||
pagerScopeTargetPeak = 0;
|
||||
pagerScopeMsgBurst = 0;
|
||||
pagerScopeLastWaveAt = 0;
|
||||
pagerScopeLastInputSample = 0;
|
||||
drawPagerScope();
|
||||
}
|
||||
|
||||
function drawPagerScope() {
|
||||
const ctx = pagerScopeCtx;
|
||||
if (!ctx) return;
|
||||
|
||||
resizePagerScopeCanvas(ctx.canvas);
|
||||
const W = ctx.canvas.width;
|
||||
const H = ctx.canvas.height;
|
||||
const midY = H / 2;
|
||||
|
||||
// Phosphor persistence: semi-transparent clear
|
||||
ctx.fillStyle = 'rgba(5, 5, 16, 0.3)';
|
||||
// Phosphor persistence
|
||||
ctx.fillStyle = 'rgba(5, 5, 16, 0.26)';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
// Smooth towards target values
|
||||
pagerScopeRms += (pagerScopeTargetRms - pagerScopeRms) * 0.25;
|
||||
pagerScopePeak += (pagerScopeTargetPeak - pagerScopePeak) * 0.15;
|
||||
|
||||
// Push current RMS into history (normalized 0-1 against 32768)
|
||||
// Keep a slow amplitude envelope for readability
|
||||
pagerScopeHistory.push(Math.min(pagerScopeRms / 32768, 1.0));
|
||||
if (pagerScopeHistory.length > SCOPE_HISTORY_LEN) {
|
||||
pagerScopeHistory.shift();
|
||||
}
|
||||
|
||||
// Grid lines
|
||||
// Grid lines (horizontal + vertical)
|
||||
ctx.strokeStyle = 'rgba(40, 40, 80, 0.4)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineWidth = 0.8;
|
||||
for (let i = 1; i < 8; i++) {
|
||||
const gx = (W / 8) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(gx, 0);
|
||||
ctx.lineTo(gx, H);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let g = 0.25; g < 1; g += 0.25) {
|
||||
const gy = midY - g * midY;
|
||||
const gy2 = midY + g * midY;
|
||||
@@ -5267,40 +5491,92 @@
|
||||
|
||||
// Center baseline
|
||||
ctx.strokeStyle = 'rgba(60, 60, 100, 0.5)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, midY);
|
||||
ctx.lineTo(W, midY);
|
||||
ctx.stroke();
|
||||
|
||||
// Waveform (mirrored)
|
||||
const stepX = W / SCOPE_HISTORY_LEN;
|
||||
ctx.strokeStyle = '#0ff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.shadowColor = '#0ff';
|
||||
ctx.shadowBlur = 4;
|
||||
|
||||
// Upper half
|
||||
// Slow envelope as context around baseline
|
||||
const envStepX = W / (SCOPE_HISTORY_LEN - 1);
|
||||
ctx.strokeStyle = 'rgba(90, 180, 255, 0.45)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < pagerScopeHistory.length; i++) {
|
||||
const x = i * stepX;
|
||||
const amp = pagerScopeHistory[i] * midY * 0.9;
|
||||
const x = i * envStepX;
|
||||
const amp = pagerScopeHistory[i] * midY * 0.85;
|
||||
const y = midY - amp;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Lower half (mirror)
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < pagerScopeHistory.length; i++) {
|
||||
const x = i * stepX;
|
||||
const amp = pagerScopeHistory[i] * midY * 0.9;
|
||||
const x = i * envStepX;
|
||||
const amp = pagerScopeHistory[i] * midY * 0.85;
|
||||
const y = midY + amp;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Actual waveform from real incoming audio samples
|
||||
const waveformPointCount = Math.min(Math.max(120, Math.floor(W / 3.2)), 420);
|
||||
if (pagerScopeWaveBuffer.length > 1) {
|
||||
const waveIsFresh = (performance.now() - pagerScopeLastWaveAt) < 700;
|
||||
const sourceLen = pagerScopeWaveBuffer.length;
|
||||
const sourceWindow = Math.min(sourceLen, 1536);
|
||||
const sourceStart = sourceLen - sourceWindow;
|
||||
|
||||
if (pagerScopeDisplayWave.length !== waveformPointCount) {
|
||||
pagerScopeDisplayWave = new Array(waveformPointCount).fill(0);
|
||||
}
|
||||
|
||||
for (let i = 0; i < waveformPointCount; i++) {
|
||||
const a = sourceStart + Math.floor((i / waveformPointCount) * sourceWindow);
|
||||
const b = sourceStart + Math.floor(((i + 1) / waveformPointCount) * sourceWindow);
|
||||
const start = Math.max(sourceStart, Math.min(sourceLen - 1, a));
|
||||
const end = Math.max(start + 1, Math.min(sourceLen, b));
|
||||
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let j = start; j < end; j++) {
|
||||
sum += pagerScopeWaveBuffer[j];
|
||||
count++;
|
||||
}
|
||||
const targetSample = count > 0 ? (sum / count) : 0;
|
||||
pagerScopeDisplayWave[i] += (targetSample - pagerScopeDisplayWave[i]) * SCOPE_WAVE_DISPLAY_SMOOTH_ALPHA;
|
||||
}
|
||||
|
||||
ctx.strokeStyle = waveIsFresh ? '#2efbff' : 'rgba(46, 251, 255, 0.45)';
|
||||
ctx.lineWidth = 1.7;
|
||||
ctx.shadowColor = '#2efbff';
|
||||
ctx.shadowBlur = waveIsFresh ? 6 : 2;
|
||||
|
||||
const stepX = waveformPointCount > 1 ? (W / (waveformPointCount - 1)) : W;
|
||||
ctx.beginPath();
|
||||
const firstY = midY - (pagerScopeDisplayWave[0] * midY * 0.9);
|
||||
ctx.moveTo(0, firstY);
|
||||
for (let i = 1; i < waveformPointCount - 1; i++) {
|
||||
const x = i * stepX;
|
||||
const y = midY - (pagerScopeDisplayWave[i] * midY * 0.9);
|
||||
const nx = (i + 1) * stepX;
|
||||
const ny = midY - (pagerScopeDisplayWave[i + 1] * midY * 0.9);
|
||||
const cx = (x + nx) / 2;
|
||||
const cy = (y + ny) / 2;
|
||||
ctx.quadraticCurveTo(x, y, cx, cy);
|
||||
}
|
||||
const lastX = (waveformPointCount - 1) * stepX;
|
||||
const lastY = midY - (pagerScopeDisplayWave[waveformPointCount - 1] * midY * 0.9);
|
||||
ctx.lineTo(lastX, lastY);
|
||||
ctx.stroke();
|
||||
|
||||
if (!waveIsFresh) {
|
||||
for (let i = 0; i < pagerScopeDisplayWave.length; i++) {
|
||||
pagerScopeDisplayWave[i] *= SCOPE_WAVE_IDLE_DECAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Peak indicator (dashed red line)
|
||||
@@ -5331,11 +5607,15 @@
|
||||
if (rmsLabel) rmsLabel.textContent = Math.round(pagerScopeRms);
|
||||
if (peakLabel) peakLabel.textContent = Math.round(pagerScopePeak);
|
||||
if (statusLabel) {
|
||||
if (pagerScopeRms > 500) {
|
||||
statusLabel.textContent = 'SIGNAL';
|
||||
statusLabel.style.color = '#0f0';
|
||||
const waveIsFresh = (performance.now() - pagerScopeLastWaveAt) < 700;
|
||||
if (pagerScopeRms > 1300 && waveIsFresh) {
|
||||
statusLabel.textContent = 'DEMODULATING';
|
||||
statusLabel.style.color = '#00ff88';
|
||||
} else if (pagerScopeRms > 500) {
|
||||
statusLabel.textContent = 'CARRIER';
|
||||
statusLabel.style.color = '#2efbff';
|
||||
} else {
|
||||
statusLabel.textContent = 'MONITORING';
|
||||
statusLabel.textContent = 'QUIET';
|
||||
statusLabel.style.color = '#555';
|
||||
}
|
||||
}
|
||||
@@ -5349,6 +5629,11 @@
|
||||
pagerScopeAnim = null;
|
||||
}
|
||||
pagerScopeCtx = null;
|
||||
pagerScopeWaveBuffer = [];
|
||||
pagerScopeDisplayWave = [];
|
||||
pagerScopeHistory = [];
|
||||
pagerScopeLastWaveAt = 0;
|
||||
pagerScopeLastInputSample = 0;
|
||||
}
|
||||
|
||||
function startDecoding() {
|
||||
@@ -5578,8 +5863,7 @@
|
||||
} else if (payload.type === 'info') {
|
||||
showInfo(`[${data.agent_name}] ${payload.text}`);
|
||||
} else if (payload.type === 'scope') {
|
||||
pagerScopeTargetRms = payload.rms;
|
||||
pagerScopeTargetPeak = payload.peak;
|
||||
applyPagerScopeData(payload);
|
||||
}
|
||||
} else if (data.type === 'keepalive') {
|
||||
// Ignore keepalive messages
|
||||
@@ -5599,8 +5883,7 @@
|
||||
} else if (data.type === 'raw') {
|
||||
showInfo(data.text);
|
||||
} else if (data.type === 'scope') {
|
||||
pagerScopeTargetRms = data.rms;
|
||||
pagerScopeTargetPeak = data.peak;
|
||||
applyPagerScopeData(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
</div>
|
||||
<select id="tileProvider" class="settings-select" onchange="Settings.setTileProvider(this.value)">
|
||||
<option value="cartodb_dark_cyan">Intercept Default</option>
|
||||
<option value="cartodb_dark_flir">FLIR Thermal</option>
|
||||
<option value="cartodb_dark">CartoDB Dark</option>
|
||||
<option value="openstreetmap">OpenStreetMap</option>
|
||||
<option value="cartodb_light">CartoDB Positron</option>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% elif offline_settings.tile_provider == 'cartodb_dark_flir' %}map-flir-enabled{% endif %}">
|
||||
<html lang="en" class="{% if offline_settings.tile_provider in ['cartodb_dark', 'cartodb_dark_cyan'] %}map-cyber-enabled{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
Reference in New Issue
Block a user