Show code
noiseMonitor = {
const container = document.createElement("div");
container.innerHTML = `
<div style="background: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); padding: 24px; border-radius: 8px; color: white; font-family: Arial, sans-serif;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h4 style="margin: 0; color: white;">Live Noise Monitor</h4>
<div>
<button id="noise-start-btn" style="padding: 8px 20px; border: none; border-radius: 4px; background: #16A085; color: white; cursor: pointer; font-size: 14px; font-weight: bold; margin-right: 8px;">Start Monitoring</button>
<button id="noise-stop-btn" style="padding: 8px 20px; border: none; border-radius: 4px; background: #E74C3C; color: white; cursor: pointer; font-size: 14px; font-weight: bold; display: none;">Stop</button>
</div>
</div>
<div id="noise-status" style="font-size: 13px; opacity: 0.8; margin-bottom: 16px;">Click Start Monitoring to activate your microphone</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px;">
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 6px; text-align: center;">
<div style="font-size: 13px; opacity: 0.8; margin-bottom: 8px;">Current Level</div>
<div id="noise-db-display" style="font-size: 42px; font-weight: bold; color: #16A085;">-- dB</div>
</div>
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 6px; text-align: center;">
<div style="font-size: 13px; opacity: 0.8; margin-bottom: 8px;">Classification</div>
<div id="noise-class-display" style="font-size: 20px; font-weight: bold; color: #3498DB; margin-top: 10px;">--</div>
</div>
</div>
<div style="margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; font-size: 11px; opacity: 0.7; margin-bottom: 4px;">
<span>0 dB</span><span>40 dB</span><span>60 dB</span><span>80 dB</span><span>100 dB</span>
</div>
<div style="background: rgba(255,255,255,0.1); border-radius: 4px; height: 24px; overflow: hidden; position: relative;">
<div id="noise-bar" style="height: 100%; width: 0%; border-radius: 4px; transition: width 0.1s, background 0.3s; background: #16A085;"></div>
<div style="position: absolute; top: 0; left: 40%; height: 100%; width: 1px; background: rgba(255,255,255,0.3);"></div>
<div style="position: absolute; top: 0; left: 60%; height: 100%; width: 1px; background: rgba(255,255,255,0.3);"></div>
<div style="position: absolute; top: 0; left: 80%; height: 100%; width: 1px; background: rgba(255,255,255,0.3);"></div>
</div>
<div style="display: flex; font-size: 10px; opacity: 0.6; margin-top: 2px;">
<span style="flex: 2;">Quiet</span>
<span style="flex: 1;">Moderate</span>
<span style="flex: 1;">Loud</span>
<span style="flex: 1;">Very Loud</span>
</div>
</div>
<div style="background: rgba(255,255,255,0.05); padding: 12px; border-radius: 6px;">
<div style="font-size: 12px; opacity: 0.7; margin-bottom: 8px;">Recent Readings (updated every second)</div>
<div id="noise-history" style="font-family: monospace; font-size: 12px; max-height: 120px; overflow-y: auto; color: rgba(255,255,255,0.8);">No readings yet</div>
</div>
<div style="font-size: 11px; opacity: 0.6; margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.15);">
WHO guidelines: <55 dB daytime / <45 dB nighttime for residential areas. Note: Smartphone microphone readings are approximate and not calibrated to a reference sound level meter.
</div>
</div>
`;
let audioContext = null;
let analyser = null;
let microphone = null;
let dataArray = null;
let animationId = null;
let readings = [];
const startBtn = container.querySelector("#noise-start-btn");
const stopBtn = container.querySelector("#noise-stop-btn");
const statusEl = container.querySelector("#noise-status");
const dbDisplay = container.querySelector("#noise-db-display");
const classDisplay = container.querySelector("#noise-class-display");
const bar = container.querySelector("#noise-bar");
const historyEl = container.querySelector("#noise-history");
function classifyNoise(db) {
if (db < 40) return { label: "Quiet (Library)", color: "#16A085" };
if (db < 60) return { label: "Moderate (Office)", color: "#3498DB" };
if (db < 80) return { label: "Loud (Traffic)", color: "#E67E22" };
return { label: "Very Loud (Construction)", color: "#E74C3C" };
}
function measureNoise() {
analyser.getByteTimeDomainData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
const normalized = (dataArray[i] - 128) / 128;
sum += normalized * normalized;
}
const rms = Math.sqrt(sum / dataArray.length);
const safeRMS = Math.max(rms, 0.0001);
const db = 20 * Math.log10(safeRMS);
const adjustedDB = Math.max(0, Math.min(100, db + 100));
const classification = classifyNoise(adjustedDB);
dbDisplay.textContent = adjustedDB.toFixed(1) + " dB";
dbDisplay.style.color = classification.color;
classDisplay.textContent = classification.label;
classDisplay.style.color = classification.color;
bar.style.width = adjustedDB + "%";
bar.style.background = classification.color;
const now = new Date();
const timeStr = now.toLocaleTimeString();
readings.push({ time: timeStr, db: adjustedDB.toFixed(1), label: classification.label });
if (readings.length > 10) readings.shift();
historyEl.innerHTML = readings.map(r =>
`<div style="display: flex; justify-content: space-between; padding: 2px 0; border-bottom: 1px solid rgba(255,255,255,0.05);"><span>${r.time}</span><span style="font-weight: bold;">${r.db} dB</span><span style="opacity: 0.7;">${r.label}</span></div>`
).reverse().join("");
animationId = requestAnimationFrame(measureNoise);
}
startBtn.addEventListener("click", async () => {
try {
statusEl.textContent = "Requesting microphone access...";
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false }
});
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
microphone = audioContext.createMediaStreamSource(stream);
analyser.fftSize = 2048;
dataArray = new Uint8Array(analyser.frequencyBinCount);
microphone.connect(analyser);
statusEl.textContent = "Monitoring active -- speak, clap, or play music to see levels change";
startBtn.style.display = "none";
stopBtn.style.display = "inline-block";
readings = [];
measureNoise();
} catch (err) {
statusEl.textContent = "Error: " + (err.name === "NotAllowedError"
? "Microphone permission denied. Please allow access and try again."
: err.message);
}
});
stopBtn.addEventListener("click", () => {
if (animationId) cancelAnimationFrame(animationId);
if (microphone) microphone.disconnect();
if (audioContext) audioContext.close();
audioContext = null;
statusEl.textContent = "Monitoring stopped. Click Start to resume.";
startBtn.style.display = "inline-block";
stopBtn.style.display = "none";
});
invalidation.then(() => {
if (animationId) cancelAnimationFrame(animationId);
if (microphone) microphone.disconnect();
if (audioContext) audioContext.close();
});
return container;
}