One folder per recording
A recording arrives as one or more files; ambiscape run turns the folder
that holds them into a finished analysis, without anything else around it.
Put the takes in a folder, describe them in a session.json, and run:
ambiscape init SESSION/ # writes a session.json skeleton
# fill in session.json: place, coordinates, notes, the spans to listen to
ambiscape run SESSION/
The folder then holds everything: the ambiscape outputs in analysis/, the
session README.md that analyze writes, listening clips in listen/, and,
if a REPORT.template.md is present, a REPORT.md filled from analysis/.
For a recording you keep making, such as a soundscape a day, the folder is the
unit that is copied, moved and deposited; nothing it needs lives elsewhere.
The stages
run goes through seven stages in order. --stage picks some of them
(repeatable) and --no-ml skips the three that need ambiscape[ml].
| Stage | What it runs | Writes |
|---|---|---|
analyze |
analyze, draft, anthrophony, mechanical, enf, background --excerpt |
summary.json, figures, README.md, annotations.draft.json, the rest |
birdnet |
birdnet with lat/lon, skipped when lat is empty |
birdnet.json |
speechgate |
speechgate on every take |
speechgate.json |
iso |
iso |
iso_indicators.json |
tags |
ml.tag_session: AudioSet posteriors every 2 s, one cache per take |
analysis/tags/*.npz |
post |
energy concentration, tag groups and timeline, handled ends against the settled recording, supply pickup (B-format), tables, listening clips | energy.json, tag_groups.json, tag_timeline.png, split.json, supply.json, tables.json, listen/ |
report |
report.fill_template on REPORT.template.md |
REPORT.md |
Each pass of the first five stages runs in its own process. This matters
for the machine-learning stages: importing tensorflow (BirdNET) before torch
hides the GPU from torch, and in separate processes neither sees the other. A
failed pass prints a !!! failed line and the run continues, so one missing
extra does not cost the rest. The tag stage resumes: a take whose cache exists
is skipped.
A night takes about an hour, most of it in birdnet, speechgate and iso;
run long sessions as a memory-capped service rather than in a terminal you
might close, and send the output to a log, which then reads as the record of
the run.
session.json
| Key | Meaning |
|---|---|
title, place, device, power, orientation, clock_note |
Free text for the report. |
notes |
Passed to analyze --notes and printed in the README. |
lat, lon |
BirdNET's location and season filter; leave lat empty to skip BirdNET. |
handling_s |
Seconds at each end resolved as handling (default 60). resolve drops a state shorter than 30 s, so 15 is the least that works for a short clip. |
excerpt_s |
Length of the characteristic excerpt (default 60). |
listen |
[["HH:MM:SS", seconds, "label"], ...]: spans exported as stereo previews, normalised, the applied gain in the filename. |
The clock in listen, and every clock in the outputs, is the session clock:
the recorder's own, moved by calibration.json where the recorder was off.
A recorder that kept its home time zone abroad needs a clock_offset_s there,
or every time in the report is an hour out.
Handled ends
The first and last minutes of a recording are usually the recordist setting
the device down and picking it up. In a quiet room one set-down knock can
carry half of the acoustic energy of half an hour, so the session LAeq
describes the knock. post therefore resolves the ends as their own state
(resolve.handling_states) and writes both into split.json, and
energy.json says how few frames carry half the energy
(analysis.energy_concentration). Read the settled state for the place.
The same split is on the command line as ambiscape resolve SESSION --by handling.
A report that cannot print a gap
REPORT.template.md is markdown with placeholders that name a JSON file in
analysis/ and a path into it:
The settled room has an LAeq of {{split.json:states.settled.laeq_dbfs}} dBFS
and a median centroid of {{summary.json:centroid_median_hz}} Hz.
{{tables.json:states}}
{{file.json:key.path:fmt}} adds a format spec (.1f, +.0f). A value
stored as text, such as a clock time or a table from tables.json, goes in as
it is; numbers print with a true minus sign. A missing key or a null value
stops the build with the placeholder's name, so a report never shows a blank
where a measurement should be, and a number in it is never typed by hand. The
prose around the placeholders is yours: run measures, the template says what
the measurement means.
Analyses that only one recording needs, such as a breathing-rate test for a
night with sleepers, stay in that folder as scripts that read and write
analysis/, and their outputs fill the same template.
From Python
from ambiscape import runner
runner.init_config("SESSION")
runner.run("SESSION", stages=["post", "report"])
runner.post("SESSION") # the post stage alone