-
Notifications
You must be signed in to change notification settings - Fork 0
Recipes
Task-oriented walkthroughs. Commands assume pip install "ambiscape[iso,ml]".
ambiscape probe NIGHT/ # sanity: takes, clock, convention
ambiscape analyze NIGHT/ --notes "living room, window closed, HVAC on"
# read NIGHT/README.md, look at analysis/overview.png:
# - steady lanes = machines; find the on/off transitions
# - anglegram ridge = where the dominant source sits
ambiscape draft NIGHT/ # regimes + events + tag hints
# listen to the hinted moments, edit -> annotations.json
ambiscape taxonomy NIGHT/ # Schaeffer map + Schafer timeline
ambiscape iso NIGHT/ # loudness/sharpness/roughnessambiscape probe FOLDER/ # prints ambix|fuma per take (from zTRK tags)No tags? Compute an azimuth histogram: a distribution hugging 0°/180° across varied content means the horizontal decode is getting a vertical channel — wrong map.
import ambiscape as asc
sess = asc.open_session("FOLDER")
x, fs = asc.read_span(sess, t0=CLAP_TIME - 2, dur=6)
print(asc.decay_time(x[:, 0], fs)) # {band: (T60, dynamic_range_dB)}Trust bands with ≥ 30 dB dynamic range where T20/T30-style fits agree. Remember the field rule: 5 s of stillness around the clap.
New in v0.10.0. When a folder holds many independent one-off recordings (one per place/date, not the 2 GB splits of one session), analyze each as its own scene:
ambiscape scenes CONTRIB/Microphone_1/ # one summary per WAV
ambiscape catalog CONTRIB/Microphone_1/analysis/scenes/ # compare themEach scene becomes a catalog-ready mini-session keyed by its filename;
catalog and longitudinal then work across them unchanged. In Python,
asc.open_recording(path) opens a single file as a one-take session.
New in v0.11.0. Extract features continuously and keep only those (no long-term audio):
pip install "ambiscape[capture]"
ambiscape capture --list-devices
ambiscape capture /data/room --device 2 --block-seconds 3600Each block is captured to a transient WAV, extracted, and the audio deleted;
each midnight rolls up a per-day summary.json. Point longitudinal at the
root for the year view. A soundfield (A-format) mic needs
aformat_matrix=<4x4> (library call). Full Raspberry Pi hardware + setup
(with an optional light sensor) is in the companion ambient-pi repo.
New in v0.10.0. Run one short session per day (as in StillStanding), then:
ambiscape longitudinal CORPUS/ --key ndsi --key bird_active_minute_fraction
# per-descriptor trend/yr, seasonal amplitude, peak month + figuresanalyze writes the recording date into summary.json; the decomposition
splits each descriptor into trend + monthly-seasonal + residual. A year is
365 tiny summary rows, so this is out-of-core however large the audio was —
the supported path for year-scale study (segment a continuous capture into
per-day sessions first).
Every analyze writes analysis/summary.json. Aggregate the whole corpus
with one command (v0.7.0):
ambiscape catalog CORPUS/ --sort diffuseness_median
# -> CORPUS/analysis/catalog.csv (session-per-row) + catalog.md (transposed)Or in Python, with ranking and outlier queries:
from ambiscape import catalog
col = catalog.collect("CORPUS/")
catalog.rank(col, "ndsi") # sessions by any descriptor
catalog.outliers(col, "azimuth_R", z=1.5) # what stands out, as z-scoresRe-run analyze after upgrading ambiscape so every session carries the
same descriptor set before cataloguing.
-
asc.pick_segments(F)or choose by ear; cut withasc.export_segment(sess, t0, dur, "seg.wav")(bit-exact, keeps the source PCM subtype) and make listenable previews withasc.stereo_preview(v0.3.0; ffmpeg-c copyworks too). -
ambiscape speechgate SEGMENTS/— every file must PASS. - Convert to FLAC (
ffmpeg -i seg.wav -c:a flac seg.flac) — lossless, half the size, keeps all channels. - Tag
ambisonic b-format ambix(Freesound detects 4-channel B-format and provides the binaural spatial player); one pack per location; coarse geotags for home locations; CC license chosen once for the series.
pip install "ambiscape[viz]"Extract a short excerpt (above), then use ambiviz's aem, anglegram, or
directogram on it directly — the excerpt is exactly the "loadable file"
ambiviz is built for.
ambiscape analyze SESSION/ # needed first (cached minspec)
ambiscape rhythm SESSION/ # sources, periods, phase, variation
# read analysis/rhythm.json; look at analysis/rhythm_overview.pngInterpreting rhythm.json:
- one entry per source: its partials, cycle period, phase clusters (strike positions within the cycle), azimuth;
-
cycle: per position — hit rate, timing SD, slow wander vs cycle-to-cycle jitter, lag-1 autocorrelation. Shared wander across positions = a common clock; independent scatter = free clapper/mechanism; -
crosstalk_suspects: phase clusters that coincide with another source's strikes. Verify withrhythm.rise_spectrum— the artifact's rise spectrum shows the other source's partials.
Worked example: the Haarlem Damiaatjes case study (Case Study Haarlem Damiaatjes).
New in v0.3.0 (states module); developed on the Haarlem-loft case study
(Case Study Haarlem Loft):
from ambiscape import background, states
lvl = states.band_level(F, (250, 1000)) # the machine's band
segs = states.state_segments(lvl, min_dur_s=120) # on/off + stability SD
states.switch_points(segs) # who pressed the button when
states.duty_cycle(segs) # fridge: ~24 min at ~50 %
on_min = ... # boolean minute masks from the segments
off_min = ...
fp = background.source_fingerprint(F, on_min, off_min)
# fp["rise_max_db"/"rise_max_hz"]: turbulence hump
# fp["peaks"], fp["comb"]: stable tonal lines, blade-comb f0 candidateThen quantify what the machine hides with background.masking_index, and
scan for schedule-bound soundmarks under/around it with
schedule.grid_scan (e.g. a church clock at the 900 s grid).
New in v0.4.0. Indoor recordings hum at 50 Hz + harmonics; the grid's actual frequency wanders ±tens of mHz and the recording logs it:
from ambiscape import enf
tr = enf.enf_track(sess, step_s=300, win_s=60, harmonics=(1, 2))
enf.enf_summary(tr)
# harmonic_agreement_mhz ~1 => genuinely the grid, not a machineBeware impostors: a rotor just under synchronous speed puts a stable line
at 49.7–49.9 Hz that looks like mains in coarse spectra. The grid line
agrees across harmonics to ~1 mHz and stays inside ±50 mHz of 50.000; a
mechanical line does neither. Forensic bonus: matched against public
grid-frequency archives, the trace timestamps the recording to the second
(cross-check for clock_offset_s).
New in v0.8.0. A pooled descriptor row averages states that never coexisted; split them:
ambiscape resolve SESSION/ --by machine --band 250,1000 # on/off states
ambiscape resolve SESSION/ --by diel --night 22,6 # day/nightambiscape analyze already does this automatically for genuinely
two-state sessions (writing states.json + a README table); the explicit
command is for a specific band or day/night. Aggregate the states across a
corpus with ambiscape catalog CORPUS/ --states.
Writes analysis/states.json with the full descriptor set per state. In
Python, states can be any intervals (from a switch-off time, a schedule, or
taxonomy annotations):
from ambiscape import resolve
res = resolve.resolve(F, {"night": [(t0, t_switchoff)],
"morning": [(t_switchoff, t_end)]})Keep the pooled row for the cross-session catalog; use the states for interpretation.
New in v0.5.0. Segment the machine's states, then rate each state's background octave spectrum:
from ambiscape import iso, states
segs = states.state_segments(states.band_level(F, (250, 1000)))
# build per-state second masks from segs, then e.g. for the on-state rows:
spec_on = iso.background_octaves_db({k: F[k][on_mask] for k in ("oct_pow",)},
pct=50, offset_db=cal["dbfs_to_dbspl"])
iso.room_criteria(spec_on) # -> NR / NC / RC + rumble/hiss classAbsolute ratings need dbfs_to_dbspl in calibration.json; without it
the numbers compare rooms recorded with one recorder+gain setup only.
New in v0.6.0. First the cache-based structural measures (whole corpus, no ML):
from ambiscape import biophony
biophony.summarize_biophony(F) # narrowband/temporal/spatial descriptorsRead them as a combination: biophony is narrowband and bursty (low
bird_temporal_entropy) and from many elevated bearings (high
bird_directional_entropy + bird_above_horizon_fraction). A steady
machine tone is narrowband but fails the temporal and spatial tests — the
peak count alone does not separate them.
Then species, only where birds are legible:
ambiscape birdnet SESSION/ --lat 52.38 --lon 4.64 --hifi-max-diffuse 0.75--hifi-max-diffuse skips masked windows (a running drone), so BirdNET
runs on the hi-fi periods a switch-off exposes; --lat/--lon enable its
location/season species filter.
Recorder clocks drift. Calibrate against any event of known wall-clock time captured in the recording — a scheduled bell, a radio time signal, a phone alarm:
// SESSION/calibration.json
{"clock_offset_s": 665.0,
"method": "town bells end 21:30:00 sharp; last strike at file t=1266 s"}Positive = the clock was slow. Applied on open_session, so re-running
analyze/taxonomy/iso re-labels every figure and report with
corrected time. (Delete analysis/features/*.npz first — cached features
store the uncorrected start.)