A build guide for a year-long room logger: a Raspberry Pi that listens to a room continuously with an ambisonic microphone and watches its light, and records not the raw audio or video but a stream of features — so a whole year of a room's sonic and luminous life fits in megabytes, with no speech or imagery kept.
It is the edge front end for ambiscape:
the Pi runs ambiscape capture to turn each hour of sound into a compact
feature file and then discards the audio, plus a light logger that samples the
room's illuminance and colour once a second. Each midnight is rolled up into a
day-summary; a year is 365 of them, ready for ambiscape longitudinal.
Developed for the AMBIENT project at fourMs / RITMO, University of Oslo.
A year of continuous 4-channel ambisonic audio at 48 kHz/24-bit is roughly 11 terabytes and is full of recognisable speech — a storage problem and a privacy problem at once. Extracting features on the device and keeping only those solves both:
- Storage. A day of 1 Hz features and light is a few megabytes; a year fits on the SD card, let alone the SSD.
- Privacy. Below the timescale of a second, the feature stream cannot reconstruct speech. What is kept is the room's behaviour — how loud, in which bands, from which direction, how bright, what colour — not its contents. Audio exists only one block at a time and is deleted after analysis; you can keep occasional short excerpts deliberately (speech-gated) for ground truth, but nothing is retained by default.
- Robustness. Each block is a self-contained unit on disk; a power cut or crash costs at most one block, and the rest of the year is intact.
This is the same non-identifying feature format that the StillStanding and Intercontinental corpora deposit, so a year from this rig drops straight into the same analysis and the same eventual public archive.
| Stream | Rate | What | How |
|---|---|---|---|
| Sound | 1 Hz features (from 8192-pt FFTs at 0.1 s hops) | level (Leq/percentiles), octave & 96-band spectra, spectral centroid/flatness, per-second direction-of-arrival, diffuseness ψ | ambiscape capture on an ambisonic mic |
| Light | 1 Hz | illuminance, and (with a spectral sensor) colour balance + a mains-flicker flag | I2C light sensor + light/light_logger.py |
| Vision | 1 Hz | brightness & unevenness, colour balance & colourfulness, where the light is (brightness centroid), spatial evenness, motion — from a downscaled frame that is then discarded | ambiscape.vision + visual/frame_features.py |
| Day rollup | 1/day | the full ambiscape descriptor set per day (a "scene") + non-identifying deposit | automatic at midnight |
All three 1 Hz streams share a clock, so the room's sound, light, and visual rhythms line up sample-for-sample — the "sound-light" (audio-visual) pairing the project is built around: the diurnal light curve beside the diurnal sound curve, a lamp switching on as an event in brightness, in colour, and in the sound of its switch, all at the same second.
See hardware.md for the full bill of materials, wiring, and
alternatives. In brief:
- Raspberry Pi 5 (4 GB is plenty; the feature extraction is ~10 small FFTs/second, trivial for the Pi 5) with an active cooler.
- A first-order ambisonic microphone. Either a soundfield mic (A-format out — converted to B-format on the Pi) into a 4-input USB audio interface with phantom power (e.g. Focusrite Scarlett 18i, MOTU, Behringer UMC404HD), or a self-contained AmbiX recorder used as a USB interface.
- A light sensor on I2C — an AS7341 (11-channel spectral: brightness and colour and flicker) is the rich choice; a TSL2591 (huge dynamic range lux) or BH1750 (cheap lux) also work. The logger auto-detects.
- A USB SSD for the data and logs (do not write continuously to the SD card — it will wear out within the year).
- Reliable power, ideally with a small UPS HAT so a brief outage doesn't corrupt a write, and the Pi's own hardware watchdog enabled.
git clone https://github.com/fourMs/ambient-pi
ROOT=/data/room sudo -E bash ambient-pi/setup/install.shThe installer sets up a Python virtual environment with
ambiscape[capture] and the light/camera libraries, enables I2C and NTP,
installs the three systemd services, and creates an unprivileged ambient
user. Then:
# 1. find your audio interface
/opt/ambient/venv/bin/ambiscape capture --list-devices
# 2. set DEVICE and ROOT in the service file
sudoedit /etc/systemd/system/ambient-capture.service
# 3. confirm the light sensor is on the bus (address shows up)
i2cdetect -y 1
# 4. start the three loggers
sudo systemctl enable --now ambient-capture ambient-light ambient-vision
# 5. watch them
journalctl -u ambient-capture -fSee setup/ and systemd/ for the scripts and unit
files.
A soundfield mic outputs A-format — four raw capsule signals — which must be matrixed into B-format (AmbiX W/Y/Z/X) before analysis. The conversion matrix is specific to your microphone and comes from its maker; drop it into the capture call:
import numpy as np
from ambiscape.capture import CaptureDaemon
M = np.array([...]) # your mic's 4x4 A->B matrix (rows W,Y,Z,X)
CaptureDaemon("/data/room", aformat_matrix=M, block_seconds=3600).run()A mic that already outputs B-format needs no matrix (pass --order fuma if it
is FuMa rather than AmbiX). Verify the encoding before you trust a year of
it: record a minute, run ambiscape probe, and check that the Z
(elevation) channel carries real energy — some devices claim ambisonics but
record silence on Z, which quietly destroys every elevation and diffuseness
number. A quick plane-wave test from a known direction confirms the decode.
The same "features, not recordings" logic extends to the visual field — a
room's look has a diurnal rhythm too — and visual/frame_features.py logs
it as a third 1 Hz stream. It grabs a small frame (160×120 by default) from a
Raspberry Pi camera (picamera2) or any USB/fisheye camera (OpenCV), extracts a
visual descriptor with ambiscape.vision, and discards the frame. What is
kept per second: brightness and its unevenness, colour balance and
colourfulness, the luma-weighted brightness centroid (where the light comes
from — a visual direction-of-arrival), the spatial evenness of the light (the
visual analogue of acoustic diffuseness), and frame-to-frame change as a
motion proxy. The downscaled frame is far too small to be recognisable
imagery, so the privacy stance matches the audio and light streams.
The installer sets this up automatically (ambient-vision.service); confirm
your camera with libcamera-hello -t 2000 (Pi camera) or by checking
/dev/video0 (USB). For richer spatial vision:
- A 360 camera gives a spatial visual field to pair with the spatial audio — the auditory–visual coupling at the heart of the AMBIENT project. But consumer 360 cameras (GoPro, Insta360) are not built for always-on operation: they overheat, fill storage, and are awkward to drive headless for a year. For continuous logging, prefer a Pi/USB camera doing feature extraction; reserve the 360 camera for short, high-quality excerpts — the rich spatial captures that ambiviz visualises — triggered occasionally or by an interesting audio event.
If the goal is to get ambisonic audio from a GoPro rather than run a separate mic: the first-generation GoPro Max does output a 4-channel spatial-audio track, and in the AMBIENT group's own comparison (Riaz et al.) it came out the best of the tested 360 cameras, broadly matching a Zoom H3-VR reference — but it is derived from the camera's capsule array and applies undocumented processing, so treat it as field-grade ambisonics, not a calibrated reference. For the Max 2, published spatial-audio specifications are not something to take on trust: whether it records true first-order B-format or a stereo/planar approximation must be verified empirically, exactly as for any ambisonic source. The test is simple and decisive, and ambiscape does it:
ambiscape probe RECORDING/ # channel count, sample format
ambiscape analyze RECORDING/ # then inspect the summary:
# - does the Z (elevation) channel carry energy, or is it ~silent?
# - does azimuth track a source moved around the mic?
# - is diffuseness plausible (not pinned at 0 or 1)?A camera whose "ambisonics" is really planar shows near-zero energy on the vertical channel and azimuths that collapse onto a front/back axis — the same failure the AMBIENT comparison found in one camera that recorded nothing on Z despite its ambisonic claims. Until a Max 2 passes that test, assume its audio is fine for listening but unproven for spatial analysis, and keep the dedicated soundfield mic as the year-long instrument. Either way, a camera and a mic are not mutually exclusive: run the calibrated mic continuously for the spatial audio, and let the camera add the visual layer.
The rig writes, per day, under the data root:
/data/room/2024-03-21/features/*.npz # hourly audio features
/data/room/2024-03-21/light/light.csv # 1 Hz light
/data/room/2024-03-21/vision/vision.csv # 1 Hz visual features
/data/room/2024-03-21/analysis/summary.json # the day, rolled up
/data/room/2024-03-21/deposit/*.tsv # non-identifying 1 Hz export
Pull the tree to a laptop (rsync over the network) and let ambiscape do the year:
ambiscape catalog /data/room # every day as one comparable row
ambiscape longitudinal /data/room --key ndsi --key leq_dbfs
# trend per year, seasonal amplitude, peak month, + figuresFor a single day, analysis/day_report.py joins the three streams onto one
time axis and draws the coupled day — sound, light and vision stacked, sharing
a clock (needs pandas and matplotlib, analysis-side):
python3 analysis/day_report.py --root /data/room --date 2024-06-21
# writes 2024-06-21/analysis/day.png and day.csvThe result shows the room's day as one picture: the sound rising from a quiet
night to a busy midday, the daylight curve with its warm dawn and cool noon,
and the visual brightness tracking the light with motion peaking when the room
is in use — the coupled sound-light-vision rhythm, and, over a year via
longitudinal, its seasons.
- Write to the SSD, not the SD card. Continuous logging will wear a card
out; point
ROOTat a USB SSD and keep the OS card read-mostly. - Keep the clock true. The installer enables
chrony(NTP); accurate absolute time is what makes the diurnal and seasonal analysis meaningful, and it spares you the recorder-clock drift that plagues battery recorders. - Enable the hardware watchdog so a hung Pi reboots itself, and set the
services to
Restart=always(they are). - Rotate logs (
capture.logand the journal) so they don't grow without bound over twelve months. - Calibrate once if you want absolute sound levels: take a single SPL
reading next to the mic and store the
dbfs_to_dbsploffset in acalibration.json; every day thereafter is calibrated. - Check in occasionally.
tail -f /data/room/capture.logand a glance at the latestsummary.jsontell you the rig is alive and sane.
light/light_logger.py the 1 Hz light logger (AS7341/TSL2591/BH1750)
visual/frame_features.py the 1 Hz visual logger (picamera2 / OpenCV)
analysis/day_report.py join + plot a day's sound/light/vision (laptop-side)
systemd/ service units for the audio, light and vision loggers
setup/install.sh one-shot Pi installer
hardware.md bill of materials, wiring, alternatives
MIT.