one beep, ten microphones

Someone in the house heard a beep a little before 12:13 AM and wanted to know what it was. I handed the question to an agent with access to the camera recorder. It came back with the sound, the source and a short list of reasons.

The answer is a smoke alarm. It sounded once, at 3417 Hz for 0.87 seconds, at 00:11:05, about two metres from the Room A camera. It was not a fire, not carbon monoxide and not a dying battery. It was a horn that went off once and stopped.

Four of the six indoor cameras, blurred, each with its own level meter. Turn the sound on: the beep lands at about seven seconds. Look at the timestamps, too. They disagree by several seconds, which is why the location had to come from loudness.

I didn’t write any of the code that found it. I gave the agent one rule at the start and one correction in the middle. Everything else it worked out, built, ran and cleaned up on its own.

The brief

The recorder is a Shinobi NVR in a virtual machine. Ten cameras, all recording audio, AAC at 16 kHz mono, in 15-minute segments.

All processing happens on the recorder. Only finished clips leave it. Raw footage never does.

That was the whole brief. The agent ran its commands through the hypervisor’s guest agent and pulled the camera list out of Shinobi’s own database. It made a scratch directory on the box, installed numpy and scipy into a private Python environment rather than the system’s, and cut the time window out of every camera’s segments with ffmpeg into mono WAV files. The recordings themselves were only ever read.

what ran where10 camerasindoorindoorindoorindoorindoorindooroutdooroutdooroutdooroutdoorAAC · 16 kHz mono15-minute segmentsshinobi nvr · vmffmpegcut the window → mono wavdetectorsteady narrow tones 0.7–7.8 kHzclustersame pitch, many rooms, 0.6 smeasurepitch · length · dBFSscratch dir + private venv · deleted afterclips15 s clipblurred 4-room reelraw footagenever leaves
fig 1Everything ran inside the recorder. The only arrow out carries clips.

When it was done it deleted the scratch directory. What came off the machine was a few candidate reels, a 15-second clip, and the blurred four-room video above, level meters burned in.

Teaching a script to hear

A beep is a narrow, steady tone. Speech, footsteps and dishes smear energy across the spectrum. So the agent wrote a detector around that one idea. It runs a spectrogram at two resolutions, 64 ms frames for sustained tones and 16 ms for short chirps. In each frame it keeps the strongest bin between 0.7 and 7.8 kHz only if that bin stands 15–18 dB above both its own usual level and the rest of the frame. Then the tone has to hold the same pitch for several frames running.

The first pass found plenty of tones. They were birds.

Outdoor cameras are full of birdsong, and birdsong is a narrow, steady tone. The detector did exactly what it was built to do. That’s where I came in, with the only question that needed a person: indoors or out? Indoors, and widen the window to 00:03–00:13.

The agent’s next move was the clever one. A real household beep is one sound heard in several rooms at once. It grouped same-pitch hits within 80 Hz and 0.6 seconds across cameras and ranked them by how many rooms heard each one. The event at 00:11:05 fell straight out: all six indoor cameras, about 30 dB above anything else.

the run · one input from a personagentpass 1 · all ten camerasthe tones it finds are birdspersonscopeindoors only · 00:03–00:13agentcluster across roomsone event, all six indoor cams, ~30 dB clearagentpurity checktwo 'repeats' at 00:13 were noise · one beepagentabsolute dBFSper-room levels → distance ratiosagenthouse logs, read-onlyno power transfer, no dropouts, no eventsagentexportclips + blurred four-room reel · raw stays
fig 2Seven passes. One of them was me.

What it is

Then it zoomed in. A narrow filter at 3417 Hz, 10 ms resolution, with a purity check against 2.9 and 3.95 kHz on either side. One continuous tone, 0.87 seconds, the same length on every camera. An FFT put the pitch at 3416–3417 Hz with no real overtones.

An earlier, looser pass had flagged two repeats at 00:13. The purity check showed they were broadband noise that happened to have energy in the band, not tones. The agent went back two days on two rooms and found no other long, pure beep at that pitch. One beep, once.

Residential smoke and CO horns are piezos in the 3–4 kHz range, loud enough to cross a house. Appliance beepers can’t reach five rooms. The pattern does the rest. A low-battery chirp comes back every 30–60 seconds for days. A fire alarm runs Temporal-3, a CO alarm runs Temporal-4, and both repeat for as long as the smoke or gas is there.

the pattern, not the pitch · secondsheard, 00:11:05heard, 00:11:05: on 0–0.87 sone 0.87 s note, then nothingfire · temporal-3fire · temporal-3: on 0–0.5 sfire · temporal-3: on 1–1.5 sfire · temporal-3: on 2–2.5 sfire · temporal-3: on 4–4.5 sfire · temporal-3: on 5–5.5 sfire · temporal-3: on 6–6.5 srepeats while the smoke lastsco · temporal-4co · temporal-4: on 0–0.1 sco · temporal-4: on 0.2–0.3 sco · temporal-4: on 0.4–0.5 sco · temporal-4: on 0.6–0.7 sco · temporal-4: on 5.7–5.8 sco · temporal-4: on 5.9–6 sco · temporal-4: on 6.1–6.2 sco · temporal-4: on 6.3–6.4 srepeats while the gas lastslow batterylow battery: on 0–0.05 snext chirp 30–60 s later012345678
fig 3Same horn, four signals. Only one of them stops after a single note.

Where it is

Arrival-time triangulation was out. Segment start times are only good to about a second, and the camera clocks drift by up to five. So the agent triangulated on loudness.

peak level of the 3417 hz tone · dBFSnoise floor−87 to −92room Aroom A: -22.9 dBFS (+0.0 dB vs A)−22.9nearestroom Broom B: -32.8 dBFS (-9.9 dB vs A)−32.83.1× fartherroom Croom C: -35.1 dBFS (-12.2 dB vs A)−35.14.1× fartherroom Droom D: -48.7 dBFS (-25.8 dB vs A)−48.7walls & doorsroom Eroom E: -67.7 dBFS (-44.8 dB vs A)−67.7walls & doorsroom Froom F: -70.5 dBFS (-47.6 dB vs A)−70.5walls & doors−100−90−80−70−60−50−40−30−20−10
fig 4The same 0.87 seconds on six cameras. Room A heard it 10 dB louder than anywhere else.

Same camera model, same stream profile, noise floors all between −87 and −92 dBFS in that band, so it treated the mics as equal. Sound falls about 6 dB per doubling of distance, so a level gap is a distance ratio: B is 3.1 times farther from the alarm than A is, and C is 4.1 times. Each ratio puts the source on a small circle around camera A, an Apollonius circle, with a radius of about a third of the camera spacing. With cameras five or six metres apart, that’s about two metres from camera A, just outside its field of view.

loudness → distance · illustrative layout1 mA vs B · 3.1×: radius 1.96 mA vs C · 4.1×: radius 1.44 mcam Acam Bcam Ccandidate: 1.7 m from camera Acandidate: 1.4 m from camera A1.7 m1.4 m2 mthe circlesB heard it 9.9 dB down:the alarm is 3.1× farther from Bthan from A. C: 12.2 dB, 4.1×.each ratio is one circle.the drawingB and C at 5.5 m from A, as inthe write-up's example. the anglebetween them is made up; thefloor plan is not published.walls make B and C quieter,so the real circles are larger.
fig 5Each level ratio draws one circle. The layout is made up, because the floor plan stays private; the circles are not.

D, E and F are down 26 to 48 dB, which is mostly walls and doors. That rules those rooms out and adds no geometry. The first version measured each camera against its own room’s background. The final one used absolute dBFS, which is the only way turning decibels into metres means anything.

Why it beeped

A smoke alarm that senses smoke or gas doesn’t play one note. It runs its full pattern for as long as the condition lasts. A single short note comes from the unit’s electronics, not its sensor. So the agent went looking for an electrical reason, and read the house’s home-automation history from 00:00 to 00:20, read-only.

what the house logged · 23:45–00:45bathroom rh50%60%70%80%23:48 · 58%00:07 · 75.8%held to ~00:4023:48 · 58%00:07 · 75.8%~00:40dashes join logged readingsups input123.3–123.9 V, flatdevices572 entities, none droppedha eventsnone 00:00–00:12beep 00:11:0523:4500:0000:1500:3000:45
fig 6The house’s own logs around the beep. Power, devices and events were flat.

UPS input sat at 123.3–123.9 V with no transfer to battery. All 572 entities kept reporting through 00:10:30–00:11:40, so nothing dropped long enough to reset a smart plug. No events at all between midnight and 00:12. Whatever happened was too small for the house to notice.

That leaves a short list. Score each one against the three things the data can test.

the theories against the dataone note,then silenceone sourceby room Afits thehouse logsverdictpower-restore beeppower-restore beep: one note, then silence — yespower-restore beep: one source by room A — yespower-restore beep: fits the house logs — yesbest fitself-check blipself-check blip: one note, then silence — yesself-check blip: one source by room A — yesself-check blip: fits the house logs — yesfits · older unitsinterconnect glitchinterconnect glitch: one note, then silence — yesinterconnect glitch: one source by room A — nointerconnect glitch: fits the house logs — yeswould be a chorusshower steamshower steam: one note, then silence — noshower steam: one source by room A — noshower steam: fits the house logs — yestiming onlylow batterylow battery: one note, then silence — nolow battery: one source by room A — yeslow battery: fits the house logs — yeswould still chirpfire or cofire or co: one note, then silence — no——wrong patternfitsdoesn't
fig 7Two theories survive every test, the data can’t split them, and both point at the same unit.

Power-restore beep. Hardwired alarms with battery backup beep once when AC power comes back after a drop. A sag of a few cycles is enough, from a compressor kicking on or a grid blip. That’s far too short for a UPS sampling once a minute or any Wi-Fi device to notice. It fits all three.

Self-check blip. The unit’s own fault check hiccups once. More common in older alarms near the end of their ten years. Also fits all three.

Interconnect glitch. A spike on the shared signal wire can make interconnected units chirp once. But then every unit chirps, and cameras near the other alarms would each have heard a loud one of their own. Room D, the only other room likely to have one, is 26 dB down. One loud source, not a chorus.

Shower steam. The bathroom humidity was high at the time, which is the only thing going for it. Steam that trips an alarm sets off the full cycle, and it doesn’t get as far as Room A.

Why 00:11:05 exactly? Nothing logged says, and the agent said so instead of guessing. A sub-second sag on one branch circuit is invisible to every sensor in the house.

Automagically

This used to be a weekend. Pull the files, write the throwaway script, fight the DSP library, squint at a spectrogram, redo it when the first idea is wrong. This time the agent read the recorder’s database, built its own sandbox, wrote an STFT detector, a cross-room clusterer, a purity filter and the geometry, read a second system’s logs, rendered a video with meters on it, and tidied up after itself. It never broke the one rule.

Its write-up even lists its own mistakes. Relative levels where it needed absolute ones. Repeats that a purity check turned back into noise. A coincidence in the humidity log that matched the time and nothing else. The one thing it couldn’t work out was a fact about the house, and that took one sentence from a person.

The requester listened to the clip and said yes, that’s the one.

The last step is a step stool. Press test on the alarm nearest Room A, listen for 3.4 kHz, and read the date on the back.