neon-noice-monitor / index.html
markburn's picture
Add 1 files
b94bbe8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Noise Monitor</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Roboto:wght@300;400;500&display=swap');
:root {
--primary: #00f0ff;
--secondary: #ff00f0;
--tertiary: #f0ff00;
--bg-dark: #0a0a1a;
--bg-light: #1a1a2e;
--bg-darker: #050510;
}
body {
font-family: 'Roboto', sans-serif;
background-color: var(--bg-dark);
color: white;
overflow-x: hidden;
background-image:
radial-gradient(circle at 10% 20%, rgba(0, 240, 255, 0.05) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(255, 0, 240, 0.05) 0%, transparent 20%);
}
.orbitron {
font-family: 'Orbitron', sans-serif;
}
.glow {
text-shadow: 0 0 10px var(--primary), 0 0 20px var(--primary);
}
.glow-secondary {
text-shadow: 0 0 10px var(--secondary), 0 0 20px var(--secondary);
}
.glow-tertiary {
text-shadow: 0 0 10px var(--tertiary), 0 0 20px var(--tertiary);
}
.btn-glow {
box-shadow: 0 0 15px var(--primary);
transition: all 0.3s ease;
}
.btn-glow:hover {
box-shadow: 0 0 25px var(--primary), 0 0 15px var(--primary);
transform: translateY(-2px);
}
.btn-glow-secondary {
box-shadow: 0 0 15px var(--secondary);
transition: all 0.3s ease;
}
.btn-glow-secondary:hover {
box-shadow: 0 0 25px var(--secondary), 0 0 15px var(--secondary);
transform: translateY(-2px);
}
.btn-glow-tertiary {
box-shadow: 0 0 15px var(--tertiary);
transition: all 0.3s ease;
}
.btn-glow-tertiary:hover {
box-shadow: 0 0 25px var(--tertiary), 0 0 15px var(--tertiary);
transform: translateY(-2px);
}
.border-glow {
border: 1px solid var(--primary);
box-shadow: 0 0 10px var(--primary), inset 0 0 10px var(--primary);
background: linear-gradient(135deg, rgba(0, 240, 255, 0.1), rgba(0, 240, 255, 0.05));
}
.border-glow-secondary {
border: 1px solid var(--secondary);
box-shadow: 0 0 10px var(--secondary), inset 0 0 10px var(--secondary);
background: linear-gradient(135deg, rgba(255, 0, 240, 0.1), rgba(255, 0, 240, 0.05));
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.7; }
50% { opacity: 1; }
100% { opacity: 0.7; }
}
.visualizer-bar {
background: linear-gradient(to top, var(--primary), var(--secondary));
width: 4px;
margin: 0 2px;
border-radius: 2px;
transition: height 0.1s ease-out;
}
.noise-level-indicator {
height: 10px;
background: linear-gradient(to right, #00ff00, #ffff00, #ff0000);
border-radius: 5px;
position: relative;
overflow: hidden;
}
.noise-level-indicator::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg,
rgba(0, 255, 0, 0.6) 0%,
rgba(255, 255, 0, 0.6) 50%,
rgba(255, 0, 0, 0.6) 100%);
animation: shine 3s infinite linear;
}
@keyframes shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.theme-selector input[type="radio"]:checked + label {
border-color: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
.peak-marker {
position: absolute;
width: 2px;
background-color: var(--secondary);
top: 0;
bottom: 0;
z-index: 10;
}
.peak-value {
position: absolute;
top: -20px;
transform: translateX(-50%);
font-size: 10px;
color: var(--secondary);
}
.cyberpunk-box {
position: relative;
overflow: hidden;
}
.cyberpunk-box::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(0, 240, 255, 0) 0%,
rgba(0, 240, 255, 0) 30%,
rgba(0, 240, 255, 0.3) 45%,
rgba(0, 240, 255, 0) 60%,
rgba(0, 240, 255, 0) 100%
);
transform: rotate(30deg);
animation: shine-overlay 6s infinite linear;
}
@keyframes shine-overlay {
0% { transform: translateX(-100%) rotate(30deg); }
100% { transform: translateX(100%) rotate(30deg); }
}
.device-selector {
background-color: var(--bg-light);
border: 1px solid rgba(0, 240, 255, 0.3);
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
width: 100%;
transition: all 0.3s ease;
}
.device-selector:hover {
border-color: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
.device-selector:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 15px var(--primary);
}
.holographic-effect {
position: relative;
}
.holographic-effect::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(0, 240, 255, 0.05) 0%,
rgba(255, 0, 240, 0.05) 50%,
rgba(0, 240, 255, 0.05) 100%
);
pointer-events: none;
z-index: -1;
}
.scanlines {
position: relative;
overflow: hidden;
}
.scanlines::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to bottom,
transparent 0%,
rgba(0, 240, 255, 0.05) 50%,
transparent 100%
);
background-size: 100% 2px;
pointer-events: none;
z-index: 1;
}
.grid-pattern {
background-image:
linear-gradient(rgba(0, 240, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.1) 1px, transparent 1px);
background-size: 20px 20px;
}
</style>
</head>
<body class="min-h-screen flex flex-col grid-pattern">
<header class="container mx-auto px-4 py-8 relative">
<div class="cyberpunk-box absolute inset-0 opacity-20"></div>
<h1 class="orbitron text-5xl md:text-6xl font-bold text-center mb-2 glow tracking-wider">
<span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">NEON</span>
<span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-500 to-pink-500">NOISE</span>
<span class="text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-yellow-400">MONITOR</span>
</h1>
<p class="text-center text-gray-400 font-light tracking-wider">REAL-TIME AMBIENT NOISE LEVEL VISUALIZATION SYSTEM</p>
</header>
<main class="flex-grow container mx-auto px-4 py-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Left Panel -->
<div class="lg:col-span-1 space-y-6">
<!-- Microphone Status -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 to-purple-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-microphone mr-3 text-purple-400"></i>
<span class="tracking-wider">MICROPHONE STATUS</span>
</h2>
<div class="flex items-center justify-between relative z-10">
<span id="micStatus" class="text-gray-300">Click start to begin</span>
<div id="micStatusIndicator" class="w-4 h-4 rounded-full bg-gray-600"></div>
</div>
<!-- Microphone Selector -->
<div class="mt-4 relative z-10">
<label for="microphoneSelect" class="block text-sm text-gray-400 mb-2 tracking-wider">SELECT INPUT DEVICE</label>
<select id="microphoneSelect" class="device-selector">
<option value="">Default Microphone</option>
</select>
</div>
</div>
<!-- Current Session -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow-secondary relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-purple-900/20 to-pink-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-clock mr-3 text-blue-400"></i>
<span class="tracking-wider">CURRENT SESSION</span>
</h2>
<div class="text-center relative z-10">
<div id="sessionTimer" class="orbitron text-3xl mb-2 glow">00:00:00</div>
<div class="text-sm text-gray-400 tracking-wider">MONITORING DURATION</div>
</div>
</div>
<!-- Controls -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 to-cyan-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-sliders-h mr-3 text-green-400"></i>
<span class="tracking-wider">CONTROLS</span>
</h2>
<div class="flex flex-col space-y-4 relative z-10">
<button id="startBtn" class="btn-glow orbitron py-3 px-6 bg-blue-600 hover:bg-blue-700 rounded-lg font-bold transition-all tracking-wider">
<i class="fas fa-play mr-2"></i> START MONITORING
</button>
<button id="stopBtn" disabled class="btn-glow-secondary orbitron py-3 px-6 bg-purple-600 hover:bg-purple-700 rounded-lg font-bold transition-all tracking-wider">
<i class="fas fa-stop mr-2"></i> STOP
</button>
<button id="resetBtn" class="btn-glow-tertiary orbitron py-3 px-6 bg-yellow-600 hover:bg-yellow-700 rounded-lg font-bold transition-all tracking-wider">
<i class="fas fa-redo mr-2"></i> RESET DATA
</button>
</div>
</div>
<!-- Threshold Settings -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow-secondary relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-purple-900/20 to-pink-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-bell mr-3 text-yellow-400"></i>
<span class="tracking-wider">NOISE THRESHOLD</span>
</h2>
<div class="space-y-4 relative z-10">
<div>
<label for="thresholdSlider" class="block text-sm text-gray-400 mb-2 tracking-wider">ALERT LEVEL (0-100)</label>
<input type="range" id="thresholdSlider" min="0" max="100" value="70" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-purple-500">
<div class="flex justify-between text-xs text-gray-400 mt-1 tracking-wider">
<span>0</span>
<span id="thresholdValue">70</span>
<span>100</span>
</div>
</div>
<div class="flex items-center">
<input type="checkbox" id="enableThreshold" class="mr-2 w-4 h-4 accent-purple-500" checked>
<label for="enableThreshold" class="text-sm tracking-wider">ENABLE THRESHOLD ALERTS</label>
</div>
</div>
</div>
</div>
<!-- Main Panel -->
<div class="lg:col-span-2 space-y-6">
<!-- Real-time Noise Level -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 to-cyan-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-volume-up mr-3 text-red-400"></i>
<span class="tracking-wider">REAL-TIME NOISE LEVEL</span>
</h2>
<div class="text-center mb-6 relative z-10">
<div id="noiseLevel" class="orbitron text-6xl md:text-8xl font-bold mb-2 glow pulse">--</div>
<div id="noiseDescription" class="text-xl text-gray-400 tracking-wider">MICROPHONE INACTIVE</div>
</div>
<!-- Visualizer -->
<div class="flex justify-center items-end h-32 mb-4 relative z-10" id="audioVisualizer">
<!-- Bars will be added dynamically -->
</div>
<!-- Noise Level Indicator -->
<div class="noise-level-indicator mb-2 relative z-10">
<div id="noiseLevelBar" class="h-full bg-green-500 rounded-lg" style="width: 0%"></div>
</div>
<div class="flex justify-between text-xs text-gray-400 tracking-wider relative z-10">
<span>QUIET</span>
<span>MODERATE</span>
<span>LOUD</span>
</div>
<!-- Stats -->
<div class="grid grid-cols-2 gap-4 mt-6 relative z-10">
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-4 border border-gray-700 hover:border-blue-400 transition-all">
<div class="text-sm text-gray-400 mb-1 tracking-wider">PEAK LEVEL</div>
<div id="peakLevel" class="orbitron text-2xl glow-secondary">--</div>
</div>
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-4 border border-gray-700 hover:border-purple-400 transition-all">
<div class="text-sm text-gray-400 mb-1 tracking-wider">AVERAGE</div>
<div id="averageLevel" class="orbitron text-2xl">--</div>
</div>
</div>
</div>
<!-- Historical Data -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow-secondary relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-purple-900/20 to-pink-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-chart-line mr-3 text-green-400"></i>
<span class="tracking-wider">HISTORICAL DATA</span>
</h2>
<div class="relative h-64 z-10">
<canvas id="noiseChart"></canvas>
</div>
<div class="flex justify-between mt-4 relative z-10">
<div class="text-sm text-gray-400 tracking-wider">LAST 4 HOURS OF DATA</div>
<div class="flex space-x-2">
<button id="smoothBtn" class="text-xs px-3 py-1 bg-gray-700 rounded hover:bg-gray-600 tracking-wider">
<i class="fas fa-wave-square mr-1"></i> SMOOTH
</button>
<button id="rawBtn" class="text-xs px-3 py-1 bg-gray-700 rounded hover:bg-gray-600 tracking-wider">
<i class="fas fa-chart-bar mr-1"></i> RAW
</button>
</div>
</div>
</div>
<!-- Peak Values Dashboard -->
<div class="bg-gray-900 bg-opacity-70 rounded-xl p-6 border-glow relative overflow-hidden scanlines">
<div class="absolute inset-0 bg-gradient-to-br from-blue-900/20 to-cyan-900/20 opacity-30"></div>
<h2 class="orbitron text-xl mb-4 flex items-center relative z-10">
<i class="fas fa-mountain mr-3 text-purple-400"></i>
<span class="tracking-wider">PEAK VALUES (LAST 4 HOURS)</span>
</h2>
<div class="relative h-64 z-10">
<canvas id="peakChart"></canvas>
</div>
<div class="grid grid-cols-3 gap-4 mt-4 relative z-10">
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-3 border border-gray-700 hover:border-blue-400 transition-all">
<div class="text-sm text-gray-400 mb-1 tracking-wider">HIGHEST PEAK</div>
<div id="highestPeak" class="orbitron text-xl glow-secondary">--</div>
</div>
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-3 border border-gray-700 hover:border-purple-400 transition-all">
<div class="text-sm text-gray-400 mb-1 tracking-wider">AVG PEAKS</div>
<div id="avgPeaks" class="orbitron text-xl">--</div>
</div>
<div class="bg-gray-800 bg-opacity-50 rounded-lg p-3 border border-gray-700 hover:border-yellow-400 transition-all">
<div class="text-sm text-gray-400 mb-1 tracking-wider">THRESHOLD BREACHES</div>
<div id="thresholdBreaches" class="orbitron text-xl">--</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="container mx-auto px-4 py-6 text-center text-gray-500 text-sm tracking-wider">
<p class="border-t border-gray-800 pt-4">NEON NOISE MONITOR v2.0 | CYBERPUNK EDITION | USES WEB AUDIO API | DATA STAYS IN YOUR BROWSER</p>
</footer>
<script>
// DOM Elements
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const resetBtn = document.getElementById('resetBtn');
const noiseLevel = document.getElementById('noiseLevel');
const noiseDescription = document.getElementById('noiseDescription');
const noiseLevelBar = document.getElementById('noiseLevelBar');
const micStatus = document.getElementById('micStatus');
const micStatusIndicator = document.getElementById('micStatusIndicator');
const sessionTimer = document.getElementById('sessionTimer');
const peakLevel = document.getElementById('peakLevel');
const averageLevel = document.getElementById('averageLevel');
const audioVisualizer = document.getElementById('audioVisualizer');
const thresholdSlider = document.getElementById('thresholdSlider');
const thresholdValue = document.getElementById('thresholdValue');
const enableThreshold = document.getElementById('enableThreshold');
const smoothBtn = document.getElementById('smoothBtn');
const rawBtn = document.getElementById('rawBtn');
const highestPeak = document.getElementById('highestPeak');
const avgPeaks = document.getElementById('avgPeaks');
const thresholdBreaches = document.getElementById('thresholdBreaches');
const microphoneSelect = document.getElementById('microphoneSelect');
// Audio variables
let audioContext;
let analyser;
let microphone;
let javascriptNode;
let isMonitoring = false;
let sessionStartTime = 0;
let timerInterval;
let currentStream;
// Data variables
let noiseData = [];
let peakNoise = 0;
let sumNoise = 0;
let countNoise = 0;
let chart;
let peakChart;
let chartType = 'line'; // 'line' or 'bar'
let threshold = 70;
let thresholdEnabled = true;
let historicalPeaks = [];
let thresholdBreachCount = 0;
// Initialize Charts
function initChart() {
const ctx = document.getElementById('noiseChart').getContext('2d');
chart = new Chart(ctx, {
type: chartType,
data: {
labels: Array(60).fill('').map((_, i) => `${i*4}m`).reverse(),
datasets: [{
label: 'Noise Level',
data: Array(60).fill(0),
borderColor: '#00f0ff',
backgroundColor: 'rgba(0, 240, 255, 0.2)',
borderWidth: 2,
pointRadius: 0,
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true,
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `Noise: ${context.parsed.y}%`;
}
}
}
},
animation: {
duration: 0
}
}
});
}
function initPeakChart() {
const ctx = document.getElementById('peakChart').getContext('2d');
peakChart = new Chart(ctx, {
type: 'bar',
data: {
labels: Array(12).fill('').map((_, i) => `${i*20}m`).reverse(),
datasets: [{
label: 'Peak Values',
data: Array(12).fill(0),
backgroundColor: 'rgba(255, 0, 240, 0.6)',
borderColor: 'rgba(255, 0, 240, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
},
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)'
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `Peak: ${context.parsed.y}%`;
}
}
}
}
}
});
}
// Initialize Audio Visualizer Bars
function initVisualizer() {
audioVisualizer.innerHTML = '';
for (let i = 0; i < 32; i++) {
const bar = document.createElement('div');
bar.className = 'visualizer-bar';
bar.style.height = '2px';
audioVisualizer.appendChild(bar);
}
}
// Update Chart with new data
function updateChart(value) {
if (chart) {
// Shift all data left and add new value
const data = chart.data.datasets[0].data;
data.shift();
data.push(value);
// Update chart
chart.update();
}
}
// Update Peak Chart with historical data
function updatePeakChart() {
if (!peakChart) return;
// Get peaks from localStorage
const storedPeaks = JSON.parse(localStorage.getItem('noiseMonitorPeaks')) || [];
const recentPeaks = storedPeaks.slice(-12); // Last 12 peaks (4 hours)
// Fill with zeros if not enough data
while (recentPeaks.length < 12) {
recentPeaks.unshift(0);
}
// Update chart data
peakChart.data.datasets[0].data = recentPeaks;
peakChart.update();
// Update stats
if (recentPeaks.length > 0) {
const maxPeak = Math.max(...recentPeaks.filter(p => p > 0));
const avgPeak = recentPeaks.filter(p => p > 0).reduce((a, b) => a + b, 0) /
recentPeaks.filter(p => p > 0).length || 0;
highestPeak.textContent = maxPeak > 0 ? maxPeak : '--';
avgPeaks.textContent = avgPeak > 0 ? Math.round(avgPeak) : '--';
// Update threshold breaches
const breaches = storedPeaks.filter(p => p > threshold).length;
thresholdBreaches.textContent = breaches;
}
}
// Store peak value
function storePeakValue(value) {
if (value < 10) return; // Don't store very low values
// Get existing peaks from localStorage
const storedPeaks = JSON.parse(localStorage.getItem('noiseMonitorPeaks')) || [];
// Add new peak (one per minute)
const now = new Date();
const lastPeakTime = storedPeaks.length > 0 ?
new Date(storedPeaks[storedPeaks.length - 1].timestamp) : null;
if (!lastPeakTime || (now - lastPeakTime) > 60000) { // 1 minute
storedPeaks.push({
value: value,
timestamp: now.toISOString()
});
// Keep only last 24 hours of data (1440 minutes)
if (storedPeaks.length > 240) { // 4 hours
storedPeaks.shift();
}
localStorage.setItem('noiseMonitorPeaks', JSON.stringify(storedPeaks));
updatePeakChart();
}
}
// Update Visualizer
function updateVisualizer(frequencyData) {
const bars = audioVisualizer.querySelectorAll('.visualizer-bar');
bars.forEach((bar, i) => {
const value = frequencyData[i] / 255;
const height = value * 100;
bar.style.height = `${height}px`;
bar.style.opacity = 0.1 + (value * 0.9);
});
}
// Get noise description based on level
function getNoiseDescription(level) {
if (level < 20) return 'SILENT';
if (level < 40) return 'VERY QUIET';
if (level < 60) return 'MODERATE';
if (level < 80) return 'LOUD';
return 'VERY LOUD';
}
// Get color based on noise level
function getNoiseColor(level) {
if (level < 30) return '#00ff00'; // Green
if (level < 60) return '#ffff00'; // Yellow
if (level < 80) return '#ff9900'; // Orange
return '#ff0000'; // Red
}
// Populate microphone devices
async function populateMicrophoneDevices() {
try {
// Get all audio devices
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputs = devices.filter(device => device.kind === 'audioinput');
// Clear existing options
microphoneSelect.innerHTML = '';
// Add default option
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Default Microphone';
microphoneSelect.appendChild(defaultOption);
// Add all available microphones
audioInputs.forEach(device => {
const option = document.createElement('option');
option.value = device.deviceId;
option.textContent = device.label || `Microphone ${microphoneSelect.length + 1}`;
microphoneSelect.appendChild(option);
});
return true;
} catch (error) {
console.error('Error enumerating devices:', error);
return false;
}
}
// Start monitoring
async function startMonitoring() {
try {
// Get selected device ID
const deviceId = microphoneSelect.value;
const constraints = deviceId ? { audio: { deviceId: { exact: deviceId } } } : { audio: true };
// Request microphone access
const stream = await navigator.mediaDevices.getUserMedia(constraints);
currentStream = stream;
// Create audio context
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 64;
// Create microphone source
microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
// Create script processor for audio processing
javascriptNode = audioContext.createScriptProcessor(2048, 1, 1);
analyser.connect(javascriptNode);
javascriptNode.connect(audioContext.destination);
// Set up audio processing
javascriptNode.onaudioprocess = function() {
if (!isMonitoring) return;
// Get frequency data for visualizer
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(frequencyData);
// Calculate overall volume level
const array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(array);
let values = 0;
for (let i = 0; i < array.length; i++) {
values += Math.abs(array[i] - 128);
}
const average = values / array.length;
const level = Math.min(Math.round(average * 2), 100); // Scale to 0-100
// Update UI
requestAnimationFrame(() => {
noiseLevel.textContent = level;
noiseLevel.style.color = getNoiseColor(level);
noiseDescription.textContent = getNoiseDescription(level);
noiseLevelBar.style.width = `${level}%`;
noiseLevelBar.style.backgroundColor = getNoiseColor(level);
// Update visualizer
updateVisualizer(frequencyData);
// Update stats
if (level > peakNoise) {
peakNoise = level;
peakLevel.textContent = peakNoise;
peakLevel.style.color = getNoiseColor(peakNoise);
storePeakValue(peakNoise);
}
sumNoise += level;
countNoise++;
const avg = Math.round(sumNoise / countNoise);
averageLevel.textContent = avg;
averageLevel.style.color = getNoiseColor(avg);
// Store data
noiseData.push({
time: Date.now(),
level: level
});
// Keep only last 4 hours of data (approx)
if (noiseData.length > 240) { // 4 hours at 1 sample per minute
noiseData.shift();
}
// Update chart every second
updateChart(level);
// Check threshold
if (thresholdEnabled && level > threshold) {
noiseLevel.classList.add('animate-pulse');
setTimeout(() => {
noiseLevel.classList.remove('animate-pulse');
}, 1000);
// Count threshold breaches
thresholdBreachCount++;
thresholdBreaches.textContent = thresholdBreachCount;
}
});
};
// Update UI
isMonitoring = true;
startBtn.disabled = true;
stopBtn.disabled = false;
micStatus.textContent = 'Microphone active';
micStatusIndicator.className = 'w-4 h-4 rounded-full bg-green-500 pulse';
// Start session timer
sessionStartTime = Date.now();
updateTimer();
timerInterval = setInterval(updateTimer, 1000);
// Initialize visualizer bars
initVisualizer();
} catch (error) {
console.error('Error accessing microphone:', error);
micStatus.textContent = 'Microphone access denied';
micStatusIndicator.className = 'w-4 h-4 rounded-full bg-red-500';
}
}
// Stop monitoring
function stopMonitoring() {
if (javascriptNode) {
javascriptNode.disconnect();
javascriptNode = null;
}
if (microphone) {
microphone.disconnect();
microphone = null;
}
if (analyser) {
analyser.disconnect();
analyser = null;
}
if (audioContext) {
audioContext.close();
audioContext = null;
}
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
currentStream = null;
}
// Update UI
isMonitoring = false;
startBtn.disabled = false;
stopBtn.disabled = true;
micStatus.textContent = 'Microphone inactive';
micStatusIndicator.className = 'w-4 h-4 rounded-full bg-gray-600';
// Stop timer
clearInterval(timerInterval);
}
// Reset data
function resetData() {
noiseData = [];
peakNoise = 0;
sumNoise = 0;
countNoise = 0;
thresholdBreachCount = 0;
peakLevel.textContent = '--';
averageLevel.textContent = '--';
thresholdBreaches.textContent = '--';
// Reset charts
if (chart) {
chart.data.datasets[0].data = Array(60).fill(0);
chart.update();
}
if (peakChart) {
peakChart.data.datasets[0].data = Array(12).fill(0);
peakChart.update();
}
// Clear localStorage peaks
localStorage.removeItem('noiseMonitorPeaks');
// Reset stats
highestPeak.textContent = '--';
avgPeaks.textContent = '--';
// Reset timer if not monitoring
if (!isMonitoring) {
sessionTimer.textContent = '00:00:00';
}
}
// Update session timer
function updateTimer() {
const elapsed = Date.now() - sessionStartTime;
const seconds = Math.floor(elapsed / 1000) % 60;
const minutes = Math.floor(elapsed / (1000 * 60)) % 60;
const hours = Math.floor(elapsed / (1000 * 60 * 60));
sessionTimer.textContent =
`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// Event Listeners
startBtn.addEventListener('click', startMonitoring);
stopBtn.addEventListener('click', stopMonitoring);
resetBtn.addEventListener('click', resetData);
thresholdSlider.addEventListener('input', function() {
threshold = parseInt(this.value);
thresholdValue.textContent = threshold;
});
enableThreshold.addEventListener('change', function() {
thresholdEnabled = this.checked;
});
smoothBtn.addEventListener('click', function() {
chartType = 'line';
if (chart) {
chart.config.type = 'line';
chart.data.datasets[0].tension = 0.1;
chart.update();
}
});
rawBtn.addEventListener('click', function() {
chartType = 'bar';
if (chart) {
chart.config.type = 'bar';
chart.update();
}
});
// Initialize
document.addEventListener('DOMContentLoaded', function() {
initChart();
initPeakChart();
initVisualizer();
// Set threshold value
thresholdValue.textContent = thresholdSlider.value;
// Load historical peaks
updatePeakChart();
// Populate microphone devices
populateMicrophoneDevices();
// Update device list when devices change
navigator.mediaDevices.addEventListener('devicechange', populateMicrophoneDevices);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=markburn/neon-noice-monitor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>