<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zero</title>
    <description>The latest articles on DEV Community by Zero (@buildwithfun).</description>
    <link>https://dev.to/buildwithfun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4144958%2Fcbf141d5-a8bc-4a3f-bce4-c4936d5f89c0.png</url>
      <title>DEV Community: Zero</title>
      <link>https://dev.to/buildwithfun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/buildwithfun"/>
    <language>en</language>
    <item>
      <title>Ai Voice Analysis</title>
      <dc:creator>Zero</dc:creator>
      <pubDate>Sun, 27 Sep 2026 00:47:06 +0000</pubDate>
      <link>https://dev.to/buildwithfun/ai-voice-analysis-13oh</link>
      <guid>https://dev.to/buildwithfun/ai-voice-analysis-13oh</guid>
      <description>&lt;h1&gt;
  
  
  Teaching Two Models to Hear a City: Building SonicSentinel AI
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;How we built a dual-model sound-event detection system with a locally trained Python&lt;br&gt;
classifier and a Google Teachable Machine model — and what we learned by forcing them&lt;br&gt;
to disagree.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word count: ~2,500.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why two models?
&lt;/h2&gt;

&lt;p&gt;SonicSentinel AI started as a competition constraint and turned into the most&lt;br&gt;
interesting design decision of the project. The rules require &lt;strong&gt;two independently&lt;br&gt;
trained models&lt;/strong&gt;: one we build and train ourselves in Python, and one trained through&lt;br&gt;
Google Teachable Machine (GTM) — a browser tool that handles its own audio frontend,&lt;br&gt;
its own feature extraction, its own training loop. The catch: the GTM model must never&lt;br&gt;
see the Python model's output. It classifies the same audio on its own, and only then&lt;br&gt;
do we compare the two verdicts.&lt;/p&gt;

&lt;p&gt;That constraint sounds bureaucratic until you understand what it buys you. A single&lt;br&gt;
model is a black box that grades its own homework. Two models trained on the same data&lt;br&gt;
through &lt;em&gt;different&lt;/em&gt; frontends form a natural ensemble: when they agree, confidence is&lt;br&gt;
real; when they disagree, that disagreement is itself information. Our alert system&lt;br&gt;
uses this directly — a "Gunshot" prediction where both models agree with high&lt;br&gt;
confidence is treated very differently from one where the models split.&lt;/p&gt;

&lt;p&gt;The comparison is deliberately simple, which is what makes it trustworthy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Class match:&lt;/strong&gt; do both models predict the same class?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top-class confidence difference:&lt;/strong&gt; |Python − GTM| for the predicted class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verdict taxonomy:&lt;/strong&gt; Strong Match, Acceptable Match, Weak Match, Model
Disagreement, Uncertain Result.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The corpus: 3,000 clips, ten classes, one honest split
&lt;/h2&gt;

&lt;p&gt;We built the dataset from scratch — roughly 300 clips per class across Machinery&lt;br&gt;
Fault, Glass Breaking, Alarm or Siren, Vehicle Horn, Animal Sound, Gunshot, Panic&lt;br&gt;
Scream, Aggression, Person Asking for Help, and Background Noise. Where real&lt;br&gt;
recordings were scarce (notably "Person Asking for Help", which is entirely synthetic&lt;br&gt;
text-to-speech, and 50 of 300 Aggression clips) we generated synthetic audio, and we&lt;br&gt;
record the real/synthetic ratio per class in the manifest.&lt;/p&gt;

&lt;p&gt;The split is frozen before any training: &lt;strong&gt;2,100 / 450 / 450&lt;/strong&gt; (train/val/test),&lt;br&gt;
stratified per class, with sha256 hashes in the manifest. A verifier script runs 28&lt;br&gt;
checks, including the one that matters most: no recording in validation or test&lt;br&gt;
contributes a segment to training — neither for the Python model nor for the GTM&lt;br&gt;
model. Every derived clip carries its parent's &lt;code&gt;audio_id&lt;/code&gt; plus a segment marker, so&lt;br&gt;
lineage is provable row by row.&lt;/p&gt;

&lt;p&gt;This sounds pedantic until you consider that a 3-second clip and its 2-second segment&lt;br&gt;
from the same recording are nearly the same data. Cutting corners here is the easiest&lt;br&gt;
way to report 95% accuracy that collapses the moment the model meets an unseen clip.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Features: 254 numbers that describe a sound
&lt;/h2&gt;

&lt;p&gt;The Python model does not listen to waveforms directly. A locked feature extractor&lt;br&gt;
(&lt;code&gt;audiofeat-1.0.0&lt;/code&gt;) converts each clip into a 254-dimensional vector: 128 mel-band&lt;br&gt;
energies, 20 MFCC means and standard deviations, MFCC deltas, 12 chroma values, plus&lt;br&gt;
spectral centroid, bandwidth, rolloff, flatness, RMS energy, zero-crossing rate,&lt;br&gt;
onset statistics, and tempo. Mel bands approximate human frequency perception; MFCCs&lt;br&gt;
compress spectral shape; onsets capture percussive attacks.&lt;/p&gt;

&lt;p&gt;The extraction is deterministic: every clip is segmented into 3-second windows&lt;br&gt;
(mode="cover"), features are averaged per segment, tempo is measured over the whole&lt;br&gt;
recording. Locking this config as a versioned JSON file was important — when we&lt;br&gt;
later retrained, the feature space could not silently drift under us.&lt;/p&gt;

&lt;p&gt;We trained five model families — SVM (RBF), Random Forest, Extra Trees, Gradient&lt;br&gt;
Boosting, and XGBoost — across a grid of hyperparameters and three seeds, 26&lt;br&gt;
candidates in total. The harness in &lt;code&gt;tuning.py&lt;/code&gt; enforces the protocol:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All selection happens on the &lt;strong&gt;validation&lt;/strong&gt; split.&lt;/li&gt;
&lt;li&gt;Cross-validation (5 folds) confirms the winner.&lt;/li&gt;
&lt;li&gt;Train+val refit, then the &lt;strong&gt;test set is scored exactly once&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The winner, &lt;code&gt;xgboost[max_depth=6, n_estimators=300]&lt;/code&gt;, reached test accuracy of&lt;br&gt;
&lt;strong&gt;0.693&lt;/strong&gt; and macro-F1 of &lt;strong&gt;0.693&lt;/strong&gt; against our floors of 0.85 / 0.80. Per class:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;F1&lt;/th&gt;
&lt;th&gt;Recall&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gunshot&lt;/td&gt;
&lt;td&gt;0.92&lt;/td&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Person Asking for Help&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glass Breaking&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vehicle Horn&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;0.68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Panic Scream&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background Noise&lt;/td&gt;
&lt;td&gt;0.66&lt;/td&gt;
&lt;td&gt;0.64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggression&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;0.49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machinery Fault&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alarm or Siren&lt;/td&gt;
&lt;td&gt;0.56&lt;/td&gt;
&lt;td&gt;0.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animal Sound&lt;/td&gt;
&lt;td&gt;0.46&lt;/td&gt;
&lt;td&gt;0.49&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is instructive. The two best classes are the two with the most distinctive&lt;br&gt;
signatures — a gunshot's broadband transient and the synthetic TTS clips' clean speech&lt;br&gt;
formants. The four weakest classes are exactly the ones that are acoustically&lt;br&gt;
heterogeneous (Animal Sound spans dogs, birds, and roosters) or confusable with&lt;br&gt;
another class (sirens vs. vehicle horns; industrial hum vs. background noise).&lt;/p&gt;

&lt;p&gt;Inference latency measured &lt;strong&gt;0.81 ms&lt;/strong&gt; per clip on CPU — the SRS allows 8 seconds for&lt;br&gt;
a 30-second clip, so the model is four orders of magnitude inside budget. The&lt;br&gt;
bottleneck in practice is feature extraction and decoding, not prediction.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where the headroom was
&lt;/h2&gt;

&lt;p&gt;Honest accounting: our first sweep missed the floors. The 254 summary numbers average&lt;br&gt;
away timing detail — precisely the detail that separates a siren's sweep from a horn's&lt;br&gt;
steady honk, or a scream's rising formant from a bird call. So we built a second&lt;br&gt;
training path on &lt;strong&gt;mel-spectrogram tensors&lt;/strong&gt; (a CNN over time × mel frames rather&lt;br&gt;
than averages), with MobileNetV3 transfer weights available for transfer learning.&lt;/p&gt;

&lt;p&gt;This is the current frontier: the classical retrain and the deep run are evaluated&lt;br&gt;
side by side, selection still on validation only. Whatever wins becomes the served&lt;br&gt;
model; the loser's metrics stay in the evidence folder. (The final numbers and the&lt;br&gt;
confusion matrix are in &lt;code&gt;python_models/metrics/classical_metrics.json&lt;/code&gt; and the&lt;br&gt;
comparison report.)&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The GTM side: same data, different brain
&lt;/h2&gt;

&lt;p&gt;The GTM samples were cut from the &lt;strong&gt;training-split recordings only&lt;/strong&gt; — 5,230&lt;br&gt;
two-second, 16 kHz segments, 404–619 per class, cut at deterministic positions. The&lt;br&gt;
cutter refuses val/test parents outright, and the manifest records each segment's&lt;br&gt;
parent id. In the browser, GTM trains its own log-mel frontend on these clips; our&lt;br&gt;
server-side predictor reproduces that frontend exactly (same sample rate, window,&lt;br&gt;
hop, mel bins, normalization), verified by comparing our reproduction against the&lt;br&gt;
browser's own predictions clip-by-clip with a 0.05 confidence tolerance and a ≥95%&lt;br&gt;
class-agreement requirement.&lt;/p&gt;

&lt;p&gt;The result is a model that cannot share our Python model's bias. When they disagree —&lt;br&gt;
for instance, the Python model says "Alarm or Siren" at 0.71 while GTM says "Vehicle&lt;br&gt;
Horn" at 0.66 — the comparison layer flags a Model Disagreement and the alert rules&lt;br&gt;
route the event to manual review instead of trusting either.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The application around the models
&lt;/h2&gt;

&lt;p&gt;Models are the easy part of a detection system. The hard parts are the paths around&lt;br&gt;
them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audio quality gating.&lt;/strong&gt; Every clip gets a Good / Acceptable / Poor / Unusable
verdict (silence ratio, clipping, level) before classification; unusable audio is
quarantined, not classified, because a confident prediction on broken audio is a
lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert state machine.&lt;/strong&gt; Open → Acknowledged/Dismissed/Escalated with invalid
transitions rejected (acknowledging a closed alert returns 422; dismissals require
a reason; re-deciding a decided review is refused).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurable rules, not code.&lt;/strong&gt; Severity scales (a five-level default, four-level
selectable), alert conditions, manual-review triggers, and retention windows all
live in JSON files under &lt;code&gt;alert_rules/&lt;/code&gt;. Security operators tune behavior without
touching a line of Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live monitoring.&lt;/strong&gt; One-to-three-second microphone windows are pushed continuously
and repeated detections of the same event are grouped, with ≤3-second processing
budget per window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every decision is auditable.&lt;/strong&gt; Config edits, review decisions, retention purges
(respecting legal holds) all land in an audit log with before/after content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Waves, spectrograms, and what the models actually see
&lt;/h2&gt;

&lt;p&gt;Before writing any model code, we spent a day just looking. The waveform view shows a&lt;br&gt;
gunshot as a single dense vertical burst and a siren as a slow amplitude wave; the&lt;br&gt;
spectrogram view shows a siren's frequency &lt;em&gt;sweep&lt;/em&gt; (a rising ribbon), a vehicle horn's&lt;br&gt;
two steady horizontal harmonics, glass breaking as a spray of vertical broadband&lt;br&gt;
lines, and speech as formant bands that move with the words.&lt;/p&gt;

&lt;p&gt;Two design decisions came directly from staring at these pictures. First, why Animal&lt;br&gt;
Sound confuses the model: a dog bark and a rooster crow look nothing alike in a&lt;br&gt;
spectrogram — different fundamental frequencies, different temporal envelope — so one&lt;br&gt;
class was asking the model to draw one boundary around several unconnected phenomena.&lt;br&gt;
Second, why sirens and horns collide: in the mel scale's upper region both classes&lt;br&gt;
show strong harmonic stacks; they differ mainly in &lt;em&gt;how the harmonics move over time&lt;/em&gt;,&lt;br&gt;
which is a property the 254 summary features average away but a CNN over mel frames&lt;br&gt;
retains. That observation drove the deep-model path more than any paper did.&lt;/p&gt;

&lt;p&gt;We also learned to distrust single measurements. A clip can have a perfect RMS level&lt;br&gt;
and still be unusable if it contains thirty seconds of silence followed by one second&lt;br&gt;
of event. So the quality verdict combines silence ratio, clipping fraction, and&lt;br&gt;
signal-to-noise estimate, and the preprocessing stage reports &lt;em&gt;why&lt;/em&gt; it flagged a clip&lt;br&gt;
— reasons that end up in the event record and the review UI, not just a boolean.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Noise robustness, false positives, false negatives
&lt;/h2&gt;

&lt;p&gt;We tested robustness the unglamorous way: by degrading our own test clips. Adding&lt;br&gt;
stationary pink noise at 10 dB SNR barely moved Gunshot or Glass Breaking recall —&lt;br&gt;
their broadband transients survive — but dropped Alarm or Siren noticeably, because&lt;br&gt;
tonal sweeps sit exactly where stationary noise lives. At 5 dB SNR, Background Noise&lt;br&gt;
recall &lt;em&gt;rose&lt;/em&gt; (everything started looking like noise) while Aggression recall fell&lt;br&gt;
through the floor.&lt;/p&gt;

&lt;p&gt;The false-positive analysis told a sharper story than the accuracy number. Most false&lt;br&gt;
"Gunshot" alerts traced back to fireworks-adjacent transients and door slams inside&lt;br&gt;
Background Noise recordings — acoustically, a legitimate confusion; a shotgun and a&lt;br&gt;
car backfire share their first 100 ms. Most false negatives for "Panic Scream" were&lt;br&gt;
distant screams at low SNR, where the spectral fingerprint is real but buried. Both&lt;br&gt;
findings fed the alert rules: gunshot alerts now require either model agreement or a&lt;br&gt;
repeat detection within the window, and distant-scream events are routed to manual&lt;br&gt;
review rather than auto-dismissed.&lt;/p&gt;

&lt;p&gt;The false-negative analysis also changed the &lt;em&gt;severity&lt;/em&gt; mapping. A missed gunshot&lt;br&gt;
that later surfaces in event history is worse than a false alarm that a human clears&lt;br&gt;
in two seconds — so the critical-class recall floor (0.85) is deliberately stricter&lt;br&gt;
than the accuracy floor, and alert thresholds for critical classes sit lower than for&lt;br&gt;
informational ones. Threshold tuning is a policy decision, and we made it explicit in&lt;br&gt;
&lt;code&gt;alert_rules.json&lt;/code&gt; rather than hiding it in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Security, privacy, and the things we refuse to do
&lt;/h2&gt;

&lt;p&gt;An audio monitoring system is a privacy instrument by definition, so the constraints&lt;br&gt;
are part of the design. Live monitoring requires explicit browser consent per session,&lt;br&gt;
recorded with the session row; sessions are stopped server-side, not abandoned.&lt;br&gt;
Retention is configurable per artifact type (event records, uploaded audio, live&lt;br&gt;
session audio) with a purge command that respects legal holds — a flagged&lt;br&gt;
investigation's audio survives retention expiry. Access is role-gated: a normal user&lt;br&gt;
sees events; only reviewers decide reviews; only administrators touch config, users,&lt;br&gt;
and retention; every privileged action lands in the audit log with actor, action,&lt;br&gt;
and content diff.&lt;/p&gt;

&lt;p&gt;On security: uploads are validated by content (magic bytes and decodability), not by&lt;br&gt;
extension; all API writes re-check authorization server-side rather than trusting the&lt;br&gt;
UI; passwords are stored hashed with per-user salts and a minimum-length policy&lt;br&gt;
enforced server-side; error envelopes return stable machine-readable codes without&lt;br&gt;
stack traces. And per the competition's integrity rules — which we agree with — no&lt;br&gt;
external generative-AI API participates in any runtime decision: the final sound&lt;br&gt;
classification comes only from the two trained models.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. What we would do differently
&lt;/h2&gt;

&lt;p&gt;Three things stand out. First, we would collect harder data &lt;em&gt;before&lt;/em&gt; training: the&lt;br&gt;
Animal Sound class should have been split into sub-classes from the start, since one&lt;br&gt;
"Animal" class spanning dogs and songbirds is a taxonomy problem, not a model problem.&lt;br&gt;
Second, we would build the mel-tensor CNN path on day one instead of treating summary&lt;br&gt;
features as a destination — they were a good baseline, but the timing information they&lt;br&gt;
discard turned out to be exactly what the weak classes needed. Third, we would&lt;br&gt;
benchmark the near-duplicate detector earlier: near-duplicate clips across classes&lt;br&gt;
( background noise captured at the same street corner ) cost us confusion we only&lt;br&gt;
diagnosed after the first sweep.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Try it
&lt;/h2&gt;

&lt;p&gt;The repository contains the full dataset (manifest + frozen split), both training&lt;br&gt;
pipelines, the converted GTM model, the configurable rule files, a 400+ test pytest&lt;br&gt;
suite, and step-by-step installation and execution instructions in &lt;code&gt;README.md&lt;/code&gt;. The&lt;br&gt;
demo video walks through upload → dual prediction → comparison → alert → manual&lt;br&gt;
review → live monitoring.&lt;/p&gt;

&lt;p&gt;Sound detection is not a solved problem, and this project is not a finished product —&lt;br&gt;
but the discipline of two independent models, one frozen split, and a comparison layer&lt;br&gt;
that treats disagreement as signal rather than noise is a pattern worth reusing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
