Noise Sentinel is a local, single-laptop acoustic anomaly detection project. It detects short, loud, low-frequency burst events from demo audio, uploaded WAV files, or optional laptop microphone capture. It stores event records in SQLite, estimates a time-of-day event rate lambda(t), and scores whether recent activity is unusual under a Poisson model.
The first version is intentionally scoped to one laptop. It answers:
- When do loud burst-like events tend to happen?
- How many events were detected?
- Is the latest activity unusual compared with the learned baseline?
- What is the probability of at least one event in the next time window?
It does not claim route prediction yet. A single fixed microphone cannot infer direction or road movement without more sensors.
flowchart LR
A["Audio input<br/>Demo, WAV upload, optional mic"] --> B["Signal processing<br/>Windows, FFT, spectrogram"]
B --> C["Feature extraction<br/>dBFS, low band, high band, burstiness"]
C --> D["Event detection<br/>Score + robust z threshold"]
D --> E["Event merge<br/>Nearby frames become one event"]
E --> F["SQLite storage<br/>data/events.sqlite"]
F --> G["Time model<br/>Circular KDE estimates lambda(t)"]
G --> H["Poisson model<br/>Expected counts and anomaly score"]
H --> I["Streamlit dashboard<br/>Charts, tables, forecast"]
F -. "Future multi-sensor data" .-> J["Spatial upgrade<br/>Hotspots, corridors, route prediction"]
- Generate synthetic noisy-bike-like demo audio.
- Load uploaded
.wavfiles. - Optionally record from the laptop microphone if
sounddeviceis installed. - Convert raw audio into interpretable acoustic features.
- Detect possible burst events using loudness, low-frequency energy, high-frequency energy, and sudden energy rise.
- Merge nearby candidate frames into one event.
- Store event logs in
data/events.sqlite. - Export events to
data/events_export.csv. - Estimate time-of-day intensity
lambda(t)with circular kernel smoothing. - Forecast the probability of at least one event in the next configurable time window.
- Score recent activity using a Poisson upper-tail anomaly test.
- Show the workflow in a Streamlit dashboard.
- It does not estimate calibrated real-world SPL unless the microphone is calibrated. The app uses
dBFS, a relative digital audio level. - It does not triangulate direction or position.
- It does not predict road routes with one laptop.
- It does not identify people or vehicles. It only detects acoustic patterns that look like loud burst events.
noisy_pattern_detect/
|-- app.py # Streamlit dashboard
|-- pyproject.toml # Project metadata
|-- requirements.txt # Python dependencies
|-- run_dashboard.ps1 # Windows helper script
|-- README.md # Project documentation
|-- data/
| `-- README.md # Local generated data lives here
|-- noisy_pattern_detect/
| |-- __init__.py
| |-- audio_capture.py # WAV loading and optional microphone capture
| |-- config.py # Detection and model parameters
| |-- demo.py # Synthetic demo audio generator
| |-- detection.py # Event scoring and event merging
| |-- models.py # KDE, Poisson, forecasting, anomalies
| |-- pipeline.py # End-to-end analysis helper
| |-- reporting.py # Summary helpers
| |-- signal_processing.py # FFT features and spectrograms
| `-- storage.py # SQLite persistence
`-- tests/
`-- test_pipeline.py # Basic demo pipeline test
From PowerShell:
cd "C:\Users\22246\Downloads\Jobs\noisy_pattern_detect"
python -m streamlit run app.pyOr:
cd "C:\Users\22246\Downloads\Jobs\noisy_pattern_detect"
.\run_dashboard.ps1Live microphone capture is optional. Install it only if you want live recording:
pip install sounddeviceThe rest of the app works with demo audio and uploaded WAV files.
| Stage | Input | Processing | Output |
|---|---|---|---|
| 1. Audio capture | Demo audio, uploaded WAV, optional microphone | Normalize to mono float audio and resample | Short audio stream |
| 2. Signal processing | Raw waveform | Windowing, FFT, band-energy extraction, spectrogram | Frequency/time features |
| 3. Event detection | Spectral features | Detection score plus robust z-score threshold | Possible noisy-burst event records |
| 4. Event storage | Event records | SQLite insert | data/events.sqlite |
| 5. Time modelling | Event timestamps | Circular KDE over hour-of-day | Expected arrival rate lambda(t) |
| 6. Anomaly detection | Observed vs expected counts | Poisson upper-tail probability | Unusual activity score |
| 7. Dashboard | Audio, events, models | Streamlit tabs and Plotly charts | Interactive local interface |
| 8. Future upgrade | Multiple sensors | Spatial KDE, DBSCAN, Markov/HMM route model | Hotspot and route prediction |
The microphone or WAV file produces a discrete signal:
x[n], n = 0, 1, 2, ...
If the sample rate is 22050 Hz, one second contains 22050 samples. The waveform gives amplitude over time, but not directly which frequencies are present.
The app splits audio into overlapping frames:
window_seconds = 0.50
hop_seconds = 0.25
Each frame is multiplied by a Hann window and transformed with the real FFT:
X[k] = sum(x[n] * w[n] * exp(-j * 2*pi*k*n/N))
P[k] = |X[k]|^2
Intuition: this turns a sound chunk into "how much energy exists at each frequency".
For each frame:
total_power = sum(P[k])
low_band_power = sum(P[k]) for 80 Hz <= f < 500 Hz
high_band_power = sum(P[k]) for 500 Hz <= f < 3000 Hz
The ratios are:
low_band_ratio = low_band_power / total_power
high_band_ratio = high_band_power / total_power
Intuition:
80-500 Hzcaptures boom and rumble energy.500-3000 Hzcaptures sharper mechanical/acoustic texture.- Ratios help avoid treating every loud broadband sound as the same thing.
The app computes RMS level:
RMS = sqrt(mean(x[n]^2))
dBFS = 20 * log10(RMS)
dBFS is a relative digital level, not calibrated real-world dB SPL.
The detector compares the log energy of each frame with the previous frame:
burstiness = max(0, log_energy[t] - log_energy[t-1])
Intuition: a sudden rise is more event-like than a steady background hum.
The frame score is:
score =
level_score
+ burst_score
+ 1.8 * low_band_ratio
+ 0.8 * high_band_ratio
A frame becomes a candidate if:
dBFS >= min_dbfs
low_band_ratio >= min_low_band_ratio
and either:
robust_z(score) >= z_threshold
or score >= absolute_score_threshold
The detector uses the median and median absolute deviation:
z = 0.6745 * (score - median(score)) / MAD
This is less sensitive to outliers than a mean/std z-score.
Candidate frames close together are merged:
merge_gap_seconds = 2.0
This counts one acoustic pass as one event rather than many overlapping frames.
Events are converted into hour-of-day values:
23.5 = 11:30 PM
0.25 = 12:15 AM
The app estimates a smooth circular intensity curve:
lambda_hat(t) = (1 / days) * sum(GaussianCircular(t - t_i, bandwidth))
Circular smoothing means an event near midnight influences both late-night and just-after-midnight estimates.
Let K be the number of detected events in a time window:
P(K = k) = (lambda * t)^k * exp(-lambda * t) / k!
For recent activity, the app compares:
observed_count
expected_count from lambda(t)
The upper-tail probability is:
P(K >= observed_count)
The anomaly score is:
anomaly_score = -log10(P(K >= observed_count))
Interpretation:
| Score | Meaning |
|---|---|
| near 0 | ordinary |
| around 1 | uncommon |
| around 2 | rare |
| 3+ | very unusual under the current model |
For a future window of length dt hours:
expected_count = lambda(current_time) * dt
P(at least one event) = 1 - exp(-expected_count)
| Parameter | Default | Meaning |
|---|---|---|
target_sample_rate |
22050 |
Audio sample rate used by the pipeline |
window_seconds |
0.50 |
Duration of each FFT analysis frame |
hop_seconds |
0.25 |
Step between frames |
low_band_hz |
80-500 |
Frequency band used for boom/rumble energy |
high_band_hz |
500-3000 |
Frequency band used for sharper acoustic energy |
min_dbfs |
-38.0 |
Minimum relative digital loudness for candidate frames |
min_low_band_ratio |
0.12 |
Required fraction of power in the low band |
z_threshold |
3.5 |
Adaptive robust z-score threshold |
absolute_score_threshold |
1.15 |
Absolute fallback score threshold |
merge_gap_seconds |
2.0 |
Merge nearby candidate frames into one event |
kde_bandwidth_hours |
1.0 |
Smoothing width for lambda(t) |
forecast_minutes |
30 |
Forecast horizon |
anomaly_window_hours |
1.0 |
Recent window used for anomaly scoring |
| Workflow stage | Files/functions |
|---|---|
| Audio capture | audio_capture.load_wav_bytes, audio_capture.record_microphone, demo.generate_demo_audio |
| Signal processing | signal_processing.extract_features, signal_processing.compute_spectrogram |
| Event detection | detection.add_detection_scores, detection.detect_events |
| Event storage | storage.init_db, storage.insert_events, storage.load_events, storage.export_events_csv |
| Time modelling | models.circular_kde_intensity, models.hourly_counts |
| Anomaly detection | models.poisson_anomaly, models.recent_window_anomaly |
| Forecasting | models.forecast_next_window |
| Dashboard | app.py |
To move from "when does noise happen?" to "where is it going?", each event must include spatial context:
event_time, sensor_id, latitude, longitude, confidence
With multiple sensors, the project can add:
- spatial KDE for heatmaps
- DBSCAN for repeated corridors
- a transition matrix such as
P(next_sensor = j | current_sensor = i) - road-graph Markov chains such as
P(segment_j | segment_i) - Hidden Markov Model / Viterbi decoding when the true road segment is uncertain
The current version builds the detection, storage, time-modelling, anomaly, and dashboard foundation first.