Skip to content

Recipes

Alexander Refsum Jensenius edited this page Jul 18, 2026 · 12 revisions

Task-oriented walkthroughs. Commands assume pip install "ambiscape[iso,ml]".

Analyze an overnight home session end-to-end

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/roughness

Verify a B-format file's channel convention

ambiscape 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.

T60 from a clap

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.

Compare many sessions

Every analyze writes analysis/summary.json. Aggregate:

import json, pathlib, pandas as pd
rows = {p.parent.parent.name: json.load(open(p))
        for p in pathlib.Path(".").glob("*/analysis/summary.json")}
print(pd.DataFrame(rows).T[["leq_dbfs", "dynamics_L10_L90",
                            "diffuseness_median", "azimuth_R"]])

Prepare excerpts for publication (e.g., Freesound)

  1. asc.pick_segments(F) or choose by ear; cut with asc.export_segment(sess, t0, dur, "seg.wav") (bit-exact, keeps the source PCM subtype) and make listenable previews with asc.stereo_preview (v0.3.0; ffmpeg -c copy works too).
  2. ambiscape speechgate SEGMENTS/ — every file must PASS.
  3. Convert to FLAC (ffmpeg -i seg.wav -c:a flac seg.flac) — lossless, half the size, keeps all channels.
  4. 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.

Hand off to ambiviz

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.

Strike-level rhythm of bells (or any periodic source)

ambiscape analyze SESSION/               # needed first (cached minspec)
ambiscape rhythm  SESSION/               # sources, periods, phase, variation
# read analysis/rhythm.json; look at analysis/rhythm_overview.png

Interpreting 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 with rhythm.rise_spectrum — the artifact's rise spectrum shows the other source's partials.

Worked example: the Haarlem Damiaatjes case study (Case Study Haarlem Damiaatjes).

Segment a machine's on/off states and fingerprint it

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 candidate

Then 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).

Fix a wrong recorder clock

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.)

Clone this wiki locally