State-resolved descriptors
A single descriptor row for a multi-state session averages soundscapes that
never coexisted. The Haarlem loft's pooled row is dominated by its 9-hour
air-pump night and barely reflects the hi-fi afternoon; its Leq, diffuseness,
and NDSI all sit between two states it represents neither of. ambiscape.resolve
splits the session by time and runs the full summary pipeline on each
state.
from ambiscape import resolve
states = resolve.machine_states(F, band=(250, 1000)) # {on: iv, off: iv}
res = resolve.resolve(F, states) # {state: full_summary}
res["machine_off"]["ndsi"], res["machine_on"]["ndsi"]
Automatic in analyze
ambiscape analyze runs resolve.auto_states after the pooled summary:
when a session is genuinely two-state (both states last long enough and the
machine band steps by ≥ 4 dB between them) it writes analysis/states.json
and appends a state-resolved table to the session README. Steady,
single-state sessions get no state rows, since one
honest row beats two spurious ones. Disable with analyze --no-resolve.
For an explicit split (a specific band, or day/night), the standalone
command writes the same states.json:
ambiscape resolve SESSION/ --by machine --band 250,1000
# machine_on: 533.7 min, Leq -47.5 dBFS, psi 0.63, events/min 0.3, NDSI -0.38
# machine_off: 168.0 min, Leq -55.1 dBFS, psi 0.82, events/min 1.8, NDSI 0.22
ambiscape resolve SESSION/ --by diel --night 22,6 # day / night instead
Discovering states
-
machine_states: on/off from a machine band viastates.state_segments; the default 250–1000 Hz band suits ventilation, other bands suit other machines.Machines outside the band are invisible
The detector only sees energy inside
band, and that includes the automatic pass inanalyze. In a Ghent kitchen overnight session (July 2026) the auto states caught the dishwasher — a 2¼ h cycle with a 188–428 Hz pump-harmonic comb, squarely in the default band — but entirely missed the fridge, which cycled 8 min on / 36 min period all night: its energy sits at 2–8 kHz (+9 dB, with a 9.2 kHz line) and leaves 250–1000 Hz flat. For fridges, freezers, and other hissers, re-run with--band 2000,8000; when two machines occupy different bands, runresolveonce per band and pass the intervals explicitly.diel_states: day / night from the wall clock (night=(22, 6)wraps midnight), for outdoor and long sessions where the diurnal cycle is the state variable.- Any intervals you supply:
resolve(F, {"label": [(t0, t1), ...]})takes absolute-second intervals, so states from the taxonomy annotations, a switch-off time, or a schedule work directly.
Slicing directly
slice_features(F, intervals) returns a sub-F valid for every
summarize_* function and every per-window analysis, so use it to run any
ambiscape measure (fingerprints, ENF, tonality) on one state alone.
full_summary(F) is the merged descriptor set (level, foreground,
ecoacoustic, spatial, biophony) for any F.
Keep the pooled row too
State-resolved rows describe the session honestly; the pooled row keeps cross-session continuity in the catalog. Report both, the pooled number for comparability and the states for interpretation.