Repository navigation
Releases: fourMs/ambiscape
Release list
0.49.0
Blind reverberation from the transients in an ordinary recording.
Added
analysis.decay_from_transientsandanalysis.transient_candidates: blind reverberation estimates from the sharpest onsets in a recording, each run throughdecay_timeon a 2 s excerpt, reported as per-band medians with interquartile ranges and a mid-band summary. For the rooms one records in without an impulse response, labelled as coarse; measure a sweep when you can.
Also fixes CITATION.cff, which still stated 0.47.0 after the 0.48.0 release.
Full changelog: https://github.com/fourMs/ambiscape/blob/main/CHANGELOG.md
0.48.0 — binaural spatial path
Binaural recordings (declared 2-channel ear signals) now get spatial estimates instead of a refusal: a lateral azimuth from the interaural time difference (GCC-PHAT over the DOA band, Woodworth-limited to ±90°, + = left) and a diffuseness from delay-compensated interaural magnitude coherence, which is invariant to per-channel linear filtering and therefore survives the HRTF. Level differences between the ears are treated as head colouring, never direction; elevation and the intensity vector remain undefined, as they must for ear signals.
Also fixes the session README recording note, which fell through to the AmbiX text for binaural sessions.
Motivated by a three-recorder soundwalk comparison (H3-VR binaural and stereo decodes vs a phone) where the binaural decode was the best-sounding and least analysable file of the trio. Seven new tests; 525 green.
0.47.0 — GPS and self-noise gating for walk mode
Walk mode learns places and honesty about its own feet.
ambiscape walk --gps track.gpx: GPX tracks give each zone a distance,
mean speed and midpoint position (an independent cross-check on the
acoustic step cadence), plus a route map coloured by zone.- Self-noise gating: footfall and wind masks feed a gated per-zone level —
what the place sounds like without the walker. On the Blindern reference
walk the walking group was 2–4 dB of its own quiet zones. - New guide page: Soundwalks (walk mode).
See CHANGELOG.md for details.
0.46.0 — walk mode
Soundwalk support: segment-first analysis of moving recordings.
- New
ambiscape walk: sub-soundscape zones from multivariate change-point
detection on the cached 1 Hz features, per-zone descriptors, step cadence
(walking-speed proxy + self-noise salience), figure-ground regime labels,
route-profile figure. - Non-stationarity diagnosis in every summary; multi-regime sessions say so
in their README where the averages are printed. - Draft keynote regimes split on spectrum, not level alone; PANNs tag budget
is now--max-tags. - Schaeffer map attributes detected events to covering named spans; the
Schafer timeline draws the soundmark edge the map already had. - Soft low-confidence descriptors carry a dagger and footnote in the README
table.
See CHANGELOG.md for details. Grew out of the first soundwalk analysed with
the toolbox (Blindern campus, 2026-08-24).
0.45.0
Freesound has required a category from its Broad Sound Taxonomy on every upload since April 2025, and it is a search facet, so it decides whether an excerpt is findable at all. This release adds the metadata sidecar that records one per excerpt, so a pack of curated AmbiX excerpts can be prepared reproducibly rather than category-by-category in a web form.
ambiscape background SESSION/ --excerpt 600 --bst ss-i --tags "ambisonic,room-tone" writes <excerpt>.wav.freesound.json beside the excerpt, carrying the category, its full name, the licence, the tags and the excerpt's own provenance. The code is validated before the export runs, so a typo costs nothing, and a non-ss-* category is refused unless --any-bst is passed.
The taxonomy is deliberately not a fourth scheme in ambiscape.taxonomy. It classifies the file — what kind of audio this is and what somebody would use it for — where Schaeffer, Schafer and soundscape ecology each ask a question of the same sound. For a session recorded in a room the answer is ss-i every time, and a field with one value is no use as a descriptor.
Full notes in CHANGELOG.md. 488 tests pass.
0.44.0
Added
-
ambiscape.examples, so the documentation runs with no recordings of your own. Every guide pointed at a session folder like2026-07-15-Haarlem, which the reader does not have, so a student following the quickstart could not run its first command.examples.demo_session(path)writes a two-take synthetic session: background alone, then an hour later two swinging bells at KNOWN azimuths of 30 and 60 degrees over the same background, four-channel AmbiX at 48 kHz.ambiscape analyzeworks against it, and the recovered bearings give the spatial code something with a right answer.A soundscape is a property of a PLACE and a synthetic session has no place in it. There is no room here, no reverberation, no traffic and no birds, so the ecological and source-domain indices will return numbers that mean nothing. The docstring says so, and so does the quickstart.
Changed
- The README no longer states that musiscape reuses this package's circular statistics and Schaeffer typology machinery. musiscape imports circular statistics from micromotion and contains no import of ambiscape anywhere in its source. Replaced with the canonical four-toolbox statement the other three now carry, plus the fact that was missing: this package keeps its own copy of six circular-statistics primitives so that it installs alone, a deliberate exception to the single-owner rule held equal to micromotion's by
tests/test_circstats_agreement.py.
Fixed
- 115 files of built documentation site were tracked in git.
.gitignorecoveredbuild/anddist/but notsite/, so a build output sat in the tree looking like a source.
ambiscape 0.43.0
Changed
io.export_segment names its output with the excerpt's wall clock, as YYYYMMDD_HHMMSS, which is the stamp open_session reads.
open_session takes a recording's start from a BWF timestamp, else a leading stamp in the filename, else the file's modification time. Exports carried none of the three, so a folder of excerpts re-opened as a session had every file dated to whenever it happened to be written: the toolbox could not read back what it had just written, and a segment handed to another toolbox arrived with no clock at all. A folder of exports is now a session on the clock it was cut from.
Pass stamp=False for an exact filename. export_segment returns the path actually written, which callers should use rather than the path they passed.
docs/interop.md states the file-boundary conventions the fourMs packages exchange audio by, and corrects musiscape's row: it depends on micromotion, not on ambiscape.
The citation drops its version number, which would go stale with every release. The concept DOI carries the identity, and each release still has its own.
Full changelog: https://github.com/fourMs/ambiscape/blob/main/CHANGELOG.md
ambiscape 0.42.0
Added
-
array.near_source_index— geometry-free inter-channel coherence in one
band, for arrays whose capsule spacing is not documented.coherence_profile
needs geometry to build its diffuse-field reference; this averages over every
channel pair and assumes nothing about which channel is where, which is the
common case for an archive that never wrote the spacing down. Being a ratio
between two channels of one device, the gain divides out: on the SINS corpus
one fixed threshold with no per-node tuning separates loud activity from an
empty room at 95.5 % across twelve uncalibrated nodes, which no level
statistic there can do.The docstring is explicit that this is not an occupancy detector, because
the obvious reading of a high value is "somebody is here" and that is wrong:
over 749 labelled minutes a television playing to an empty room gives the
highest median of any class (0.717, above a vacuum cleaner's 0.678 and above
every class with a person in it), while a person working quietly gives 0.365
against 0.247 for an empty room. It detects a near sound source. A
loudspeaker is one; a silent person is not. -
spatial.frame_reference_test--- is a bearing fixed to the recorder or to
the world? Given a bearing series in the recorder's frame and a heading
series in world coordinates, it returns the circular concentration in each
frame, their ratio, the chance floor1/sqrt(n)that an R has to beat, and
aframelabel of"rig","world"or"neither". An optional
control_degcarries a positive control through the same arithmetic,
because a test that answers "rig" cannot otherwise be told apart from a
method that answers "rig" whatever it is fed. Weighted,nis the effective
count(sum w)^2 / sum w^2.It exists because a body-worn first-order recorder carried through 300
recording days and seven kinds of place --- corridors, living rooms, an
auditorium, a train --- put its loudest bearing at R = 0.813 in its own
frame against 0.268 in compass coordinates, chance being 0.058, with the
seven groups' mean bearings spanning 18 degrees between them. The wearer's
own sway axis, as the positive control, gave 0.490 against 0.194.
Re-decoding with the correct channel convention softened this to 0.393
against 0.144 and widened the group spread to 45 degrees in a physically
sensible order: a wrong decode makes it worse and is not the cause of it,
and no decode answers the frame question.azimuth_organizationand
directional_entropynow say as much in their own docstrings, since both
describe the rig rather than the place whenever the rig travels with its
subject.
Documentation
entrain.directional_correlation: rotation invariance does not extend to
reflection, and|rho|puts the blind spot exactly where a handedness error
lands. Mirroring one angle series flips the coefficient's sign and leaves
its magnitude untouched --- exactly, to machine precision --- so the advice
to judge coupling by|rho|makes an inverted recorder invisible. A
rotation search cannot find one either: a reflection is not in the set being
searched, so the search returns a poor best fit rather than a complaint. The
docstring also notes that a coupling between an audio bearing and a body's
motion says nothing about the soundscape untilspatial.frame_reference_test
has been run, because a recorder riding on the same body is partly measuring
that body's posture.entrain._shift_p: the shifts are the null, never the answer. It is a
short step from that loop to quoting the best-fitting shift's statistic,
and that number is positive by construction, rising with the number of
shifts tried and with how smooth the two series are. On a year of daily
recordings the best of 72 circular shifts scored higher on deliberately
mismatched day pairs than on real ones, 0.599 against 0.494 --- by which
point three separate hypotheses about the apparatus had been tested, and
none of them was the problem.io.channel_order: the default is a guess, and a wrong guess looks like
data. Two independent re-decodes of one year of the same recordings
differed by a median of about 48 degrees per day, with year-long directional
concentrations of 0.815 and 0.393; both series were entirely presentable and
which one was faithful was never settled. A decode is validated by an
outside fact --- a source at a known bearing, a pass-by of known direction,
an independent heading --- not by looking sensible.enf.enf_summary:coverageis not a proxy for where a recording was
made. Across 365 daily recordings in seven kinds of place the group medians
ran 0.590 to 0.475, a range of 0.115 against a within-group interquartile
width of 0.260, with Kruskal--Wallis H = 3.7 at p = 0.72; outdoor and
semi-open ranked sixth of seven, in among the indoor groups. The ENF
measurement itself was in excellent health over the same year --- the grid
recovered on 364 of 365 days at a median 49.9913 Hz --- which is what makes
this the durable kind of error: a good measurement of the wrong thing.
Coverage tracks the recording (gain, wind, noise floor) and is tied to
win_s,step_sandmin_rise_db, so two coverages compare only where all
three match.biophony: machinery is one mechanism for a false species and not the
mechanism. This module blamed BirdNET's indoor failures on low-frequency
machinery, from a corridor that returned Great Bitterns. A second corpus of
37 sessions put a Long-eared Owl in 24 of them, and its association with the
mechanical index ran the other way --- median 0.066 in owl sessions against
0.292 without, a factor of 4.5 at Mann--Whitney p = 0.00039. Those false
detections sat in the quiet, not in the noise. Two checks are added that the
machinery story would not have caught: read the detection rate rather than
the entry, since a regionally plausible species at an implausible rate is
the signature; and check the calendar against the range, since a Common
Swift in October disqualifies itself on a fact nothing acoustic can rescue.
ambiscape 0.41.0
Changed
starss.frame_azimuthsreturns three values, not two. It now yields
(az_deg, energy, diffuseness); callers unpacking two will break.
Diffuseness is1 - |I| / Eper frame, returned so a caller can withhold an
estimate where the field is too diffuse to carry a direction. The function
also takesmethod="intensity"(the default, unchanged behaviour) or
"energy", and asub_ssub-frame length.onset_leadtakes a crossing fraction per modality. New optional
first_riseandsecond_risearguments; the default remains the symmetric
rise=ONSET_RISE, so existing calls are unaffected. Motion and audio series
do not reach a given fraction of their own range at comparable moments, and
one number for both was hiding that.AUDIO_RISEis no longer used as a default. It remains exported at 0.75,
now labelled provisional.
Fixed
- The justification for the audio crossing fraction was wrong, and the error
was the kind that reads as validation. The figure quoted for 0.75 —
landing within 0.01 s where 0.25 lands 1.78 s early — compares the rule
against another algorithmic onset, not against anyone's ear or eye; the
word "hand-checked" attached to it somewhere between the measurement and the
docstring. The agreement is also partly an artefact: the two passes used dB
and linear energy, on which the same fraction lands tens of frames apart, so
the 0.01 s was two mismatches cancelling.MOTION_RISEis validated (median
0.06 s against onsets marked by eye);AUDIO_RISEis not, and the docstring
now says not to publish a figure that depends on it.
Documentation
floor_suspicion's justification was written from one room and stated as a
property of a corpus. Re-measured across all 84 SINS node-sessions: median
spread per octave band runs 5.17, 4.54, 2.76, 2.96, 1.85, 1.54, 0.51, 0.34,
0.29 dB from 32 Hz to 8 kHz. The old claim that "every band below 1 kHz
varies by 2.4--5.3 dB" is true of that room and false of the corpus, where
500 Hz and 1 kHz sit at 1.85 and 1.54. The separation the check actually
relies on --- abovehf_min_hz, 0.29--0.51 dB against a 1.5 dB threshold ---
is intact, so no default changed and no behaviour changed. The docstring now
carries the corpus figures and notes that the check is conservative at 2 kHz,
whose 95th percentile of 1.67 dB escapes flagging in a minority of sessions.floor_suspicioncannot be cited as evidence about SINS, because its
threshold was chosen from SINS. Recorded in the docstring and in the wiki's
Design Decisions, because the flag reads like independent confirmation and is
not.modulation.profilecannot resolve a period longer than a quarter of the
record, and does not say so. Each scale's band starts at four cycles, so a
one-day session's macro band begins at 21600 s and a diurnal peak falls
outside it; the function returns the strongest thing in the remaining band
without warning. The module docstring now says this and points at the
stitch-the-week-first approach, with the two controls such a record needs.
ambiscape 0.40.0
Changed
circstats.circ_corrreturns a dict, not a float, and re-exports
micromotion.circular.circ_corr. The two toolboxes carried separate
copies of the Jammalamadaka–SenGupta formula under the same name with
different return types, so a caller who swapped the import got an object
where a number belonged. The values had agreed to 1e-12 and a test pinned
them, which caught drift in the arithmetic and could never catch the shape.
micromotion owns circular statistics in this family, so ambiscape now
defers. Breaking: callers wanting the coefficient want
circ_corr(a, b)["r"].entrain.circular_correlationis updated; thep
it reports is still the shift-surrogate one, not the parametricpthe dict
now carries. micromotion is imported lazily, asambiscape.musicreaches
musiscape, so ambiscape stays importable without it.micromotionis declared, as thecircularextra.circ_corrneeds it and nothing said so;
pip install ambiscape[circular].entrain.directional_correlationgoes through it and now says
so in its own docstring. The three tests that reach it skip where micromotion is absent, so
ambiscape's suite still passes installed alone — 451 passed, 7 skipped, against 463 with it.tests/test_circstats_agreement.pynow checks identity rather than
agreement for this function: same keys, same values, so a second copy
cannot quietly reappear.
Documentation
series_onsetno longer advises 0.75 across the board. Its evidence for
that fraction --- landing within 0.01 s of hand-checked onsets, where 0.25
lands 1.78 s early --- was gathered on audio, and the same docstring then
recommended it for the motion seriesonset_leadapplies the identical rule
to. Checked against motion onsets marked by eye from video frames, blind to
every computed value, the recommendation is wrong in the other direction:
0.10 and 0.25 land within a median 0.06 s of what a viewer calls the
beginning, while 0.50 and 0.75 are late on every clip checked, by a median
0.46 s and 0.66 s. Audio carries a noise floor a low fraction triggers on;
motion has a still lead-in a high fraction sits through. One fraction applied
to both is therefore wrong for at least one of them, and the bias
cancellationonset_leadrests on is an accident of how far two errors
match rather than a property of the rule.series_onset's high end is one frame, and that is now stated.hiis
s.max(), which sets the crossing level for the whole series: varying only
risebetween 0.20 and 0.30 across 365 clips moves the onset more than half
a second on 31 of them and more than a second on 9, worst case 5.08 s. The
90th percentile roughly halves that for a median shift of 0.08 s and does not
fix it.