It started with a wedding venue across the street. Summer weekends end at three in the morning with a bass line coming through closed windows, and a complaint to the town hall needs more than "it was loud again". It needs dates, times, levels, and ideally a recording, so that a municipal technician comes to measure on the right night instead of a quiet one.
So I built a measuring station instead of keeping a diary. A Raspberry Pi with a USB microphone measures the A-weighted level every second, sends it to a small web service with a public map and chart, and records five-minute clips when the level stays high. Then I added the part that turned out to be the most interesting: the station finds out which noise regulations apply where it stands and draws the legal limit, with its own day, evening and night periods, on the chart.
Measuring like a sound level meter, on a €35 computer
A noise limit in dB(A) is a precise thing, and a homemade meter is only useful if it computes the same quantity. The station follows the definitions in IEC 61672-1, the sound level meter standard:
- A-weighting is the standard's analog filter (poles at 20.6 Hz, 107.7 Hz, 737.9 Hz and 12.2 kHz) turned into a digital filter with the bilinear transform and run as second-order sections at 48 kHz. A test checks it against the standard's table from 31.5 Hz to 8 kHz.
- LAeq, the energy average over each second, and LAmax, the loudest 125 ms within it.
- Averages are energy averages. One second at 70 dB and one at 40 dB average to 67 dB, not 55. The server stores 10L/10 per row so SQL can average it directly for any chart bucket.
flowchart LR
mic["USB microphone\n48 kHz"] --> meter["A-weighting\nLAeq, LAmax per second"]
meter --> rec["Event detector\nLAeq,10s vs trigger\n150 s pre-roll buffer"]
rec --> spool[("SD card spool\nFLAC + SHA-256")]
meter --> up["Uploader"]
spool --> up
up -->|HTTPS| api["FastAPI + SQLite"]
api --> web["Public map and chart"]
api -->|"behind login"| owner["Owner: clips, spectrum"]
api -->|"position"| osm["Nominatim\ntown + region"]
osm --> rules["regulations.json\nstrictest limit per minute"]
rules -->|"trigger in force"| up
The audio itself never leaves the Pi except around an event. The agent keeps the last few minutes in a ring buffer, so a clip starts 150 seconds before the moment the level crossed the trigger: you hear the build-up, not just the peak. Each clip is lossless FLAC with a SHA-256 hash computed on the device and checked again by the server, and a cooldown of 15 minutes means a four-hour party gives about a dozen clips instead of 48 back-to-back ones. If the WiFi drops, everything waits on the SD card and is sent later.
The first event rule failed a real test
The first version started an event when every one of 10 consecutive seconds was above the trigger. It sounded reasonable and it missed the very first real test: people talking near the microphone produced 54 loud seconds in ten minutes, but never more than four in a row, because speech and music have natural pauses. The rule now uses the energy average over the last 10 seconds, LAeq,10s, which is how noise limits are actually defined. A door slam still does not start a recording; a conversation or a song does.
Which limit? Three authorities, three timetables
The first version had one number per station: "limit 55 dB(A)". That is wrong in two ways. Limits change with the time of day, and in Spain they are set by several authorities at once. For a street in Pamplona, three texts apply (a town without a noise ordinance of its own keeps the first two):
| Source | Periods | Quiet residential area, dB(A) |
|---|---|---|
| Real Decreto 1367/2007, annex III, table B1 (national) | day 7-19, evening 19-23, night 23-7 | 55 / 55 / 45 |
| Decreto Foral 135/1989, art. 15.1 (Navarra) | day 8-22, night 22-8 | 55 / 45 |
| Pamplona's activities ordinance, art. 9.2 (town) | day 8-22, night 22-8 | 65 / 55 at the venue's property line |
Each authority can only tighten what the others allow, so the station combines them minute by minute over a week and keeps the strictest value. For a quiet residential street the result is 55 dB(A) from 8:00 to 22:00 and 45 dB(A) from 22:00 to 8:00: Navarra's longer night with the national night value. For a busier residential street it is more interesting, because Navarra allows 60/50 there while the national table stays at 55/45:
Nobody would write that timetable by hand, and it falls out of the combination automatically.
From coordinates to a town hall
When a station gets a position (from a map in the "Add new device" form, by hand, or from the WiFi access points it can see), the server asks OpenStreetMap's Nominatim for the municipality, region and country. The rules live in one JSON file, each with the place it covers, its periods, its value for each type of area (the categories the regulations themselves use: hospitals and schools, quiet residential, residential with traffic, commercial, leisure, industrial, rural) and a link to the official text. The owner picks the type of area; everything else follows from the position.
The weekly schedule then drives the whole site. It produces the stepped limit line and grey night bands on the chart, the pink shading where the level was over the limit in force at that moment, the time over the limit per period in the last 24 hours, and the classification of each recorded event against the limit when it started. A 50 dB(A) event at 23:30 is now "+5 over" while the same level at noon is "within". It also drives the device: with every upload the server returns the recording trigger, the limit in force plus an offset, so the station records more readily at night without anyone changing a setting.
Honest about what this is
An official assessment measures LKeq over the whole period with a certified meter, adds penalties for tonal or impulsive sound, and some ordinances measure at the property line or inside the home with the windows closed. A USB microphone is not a certified class 1 meter, and in Spain no USB microphone makes a measurement legally valid on its own. The station compares one-second readings with the limit in force, which is exactly what is needed to find the nights worth an official measurement and to show a pattern, and the page says so under the limits table.
What broke along the way
- Navarra is not ES-NA. The rule matched on the ISO 3166-2 region code and never fired: Nominatim returns
ES-NCfor the Comunidad Foral de Navarra, whileES-NAis the province. One unit test with the real lookup result caught it. - The free WiFi location database did not know the street. BeaconDB had none of the access points around the station and only offered an IP-based guess 25 km wide. The service now refuses IP guesses outright, since they place the station where the internet provider routes traffic, and uses Google's Geolocation API when a key is configured.
- First boot raced the system updater. The install step ran while the OS was still running its own package update and failed on the dpkg lock. A lock timeout on
apt-getfixed it. - A failed copy deleted the only key. A command chained a file copy to the Pi with deleting the local config; the copy failed and the delete did not care. Device keys are stored only as hashes, so there was nothing to recover: the station was re-provisioned with a new identity, and the copy now streams over SSH instead.
The stack, briefly
What it costs to run
No extra cloud cost: the service is one small container on a host that already runs other services, the map tiles, reverse geocoding and WiFi location lookups are free at this volume, and there are no AI calls. Storage grows by about 1.4 GB of levels and 3 GB of clips per station per year. The hardware is about €160 per station with a measurement microphone that ships with its own calibration file, which can then be checked against an acoustic calibrator that carries an accredited certificate.
Try it
The demo has five simulated stations with synthesised sound, each following its own local time and its own rules: New York, the Amazon rainforest (on a fixed limit, since nature is not regulated), Madrid by the stadium on match nights, a concert in Sydney, and a Barcelona street next to a wedding venue. The "Add new device" form works there too: click anywhere on the map and it looks up the rules for that town.
See it live
Real IEC 61672 level definitions, real regulations with links to the official texts, and a station on a real balcony waiting for its microphone.
Open the demo → Live service →About the cover image: a balcony view of a residential street at night, with a level trace rising above the rooftops. ImageModelstability.stable-image-core-v1:1 · AWS Bedrock (us-west-2)Prompt“Editorial illustration, a quiet residential street at night seen from a balcony, a small weatherproof microphone station on the railing, glowing gold sound waves rising from a lit building across the street, a gold stepped line chart floating above the rooftops, deep navy background, minimalist flat vector illustration style, clean geometric linework, no text, no logos”