Skip to content

Releases: fourMs/ambiscape

0.49.0

Choose a tag to compare

@alexarje alexarje released this 03 Sep 20:05

Blind reverberation from the transients in an ordinary recording.

Added

  • analysis.decay_from_transients and analysis.transient_candidates: blind reverberation estimates from the sharpest onsets in a recording, each run through decay_time on 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

Choose a tag to compare

@alexarje alexarje released this 31 Aug 21:13

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

Choose a tag to compare

@alexarje alexarje released this 30 Aug 05:28

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

Choose a tag to compare

@alexarje alexarje released this 30 Aug 04:48

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

Choose a tag to compare

@alexarje alexarje released this 23 Aug 09:42

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

Choose a tag to compare

@alexarje alexarje released this 19 Aug 09:34

Added

  • ambiscape.examples, so the documentation runs with no recordings of your own. Every guide pointed at a session folder like 2026-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 analyze works 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. .gitignore covered build/ and dist/ but not site/, so a build output sat in the tree looking like a source.

ambiscape 0.43.0

Choose a tag to compare

@alexarje alexarje released this 16 Aug 10:32

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

Choose a tag to compare

@alexarje alexarje released this 14 Aug 09:14

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 floor 1/sqrt(n) that an R has to beat, and
    a frame label of "rig", "world" or "neither". An optional
    control_deg carries 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, n is 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_organization and
    directional_entropy now 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 until spatial.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: coverage is 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_s and min_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

Choose a tag to compare

@alexarje alexarje released this 13 Aug 16:04

Changed

  • starss.frame_azimuths returns three values, not two. It now yields
    (az_deg, energy, diffuseness); callers unpacking two will break.
    Diffuseness is 1 - |I| / E per frame, returned so a caller can withhold an
    estimate where the field is too diffuse to carry a direction. The function
    also takes method="intensity" (the default, unchanged behaviour) or
    "energy", and a sub_s sub-frame length.
  • onset_lead takes a crossing fraction per modality. New optional
    first_rise and second_rise arguments; 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_RISE is 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_RISE is validated (median
    0.06 s against onsets marked by eye); AUDIO_RISE is 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 --- above hf_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_suspicion cannot 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.profile cannot 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

Choose a tag to compare

@alexarje alexarje released this 12 Aug 20:16

Changed

  • circstats.circ_corr returns 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_correlation is updated; the p
    it reports is still the shift-surrogate one, not the parametric p the dict
    now carries. micromotion is imported lazily, as ambiscape.music reaches
    musiscape, so ambiscape stays importable without it.
  • micromotion is declared, as the circular extra. circ_corr needs it and nothing said so;
    pip install ambiscape[circular]. entrain.directional_correlation goes 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.py now checks identity rather than
    agreement for this function: same keys, same values, so a second copy
    cannot quietly reappear.

Documentation

  • series_onset no 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 series onset_lead applies 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
    cancellation onset_lead rests 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. hi is
    s.max(), which sets the crossing level for the whole series: varying only
    rise between 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.