Metadata-Version: 2.4
Name: proximal-ladder
Version: 0.9.0
Summary: Render any sound at any simulated distance: image-source room, rigid-sphere near-ear correction, gradient-microphone proximity, BS.1770 loudness clamp.
Author: Rishi Yildiz
License-Expression: MIT
Keywords: psychoacoustics,auditory distance,near field,room acoustics,stimulus generation,proximity effect,loudness
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: soundfile>=0.12
Requires-Dist: pyloudnorm>=0.1
Requires-Dist: mosqito>=1.2.1
Requires-Dist: pyroomacoustics>=0.7
Requires-Dist: matplotlib>=3.7
Requires-Dist: tomli>=1.1; python_version < "3.11"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# proximal-ladder

Render **any sound** at **any simulated distance** from a listener, in a
room you design, with a head and a microphone you can set.

For each distance you get one mono file: the sound as the near ear of a
listener in the room receives it. Every file is loudness-matched, so only
the distance cues change. The exception is a file whose true peak would
exceed −1 dBTP at the target loudness: it is turned down, a warning says
so, and its loudness column shows the level. Each ladder comes with a
verification table and a `params.json` that reproduces it exactly.

```bash
pip install proximal-ladder
```

**Status: beta (0.9).** The models are tested and verified. Names and
defaults may still change before version 1.0.

## Quick start

```python
import proximal_ladder as pl

lad = pl.ladder("kettle.wav",
                distances=[3, 1, 0.3],        # metres from the head centre
                room_size=[8, 5, 3],          # length x width x height of the room, metres
                head_position=[2, 2.5, 1.2],  # where the head is: x, y, ear height (metres)
                rt60=0.5,                     # reverberation time, seconds (default 0.6)
                head_radius=0.08)             # metres (default 0.0875)
print(lad)                                    # summary table
lad.save("results/", formats=["wav", "mp3"])  # audio + verification.csv + params.json

pl.ladder("kettle.wav").save("results/defaults/")   # everything at its default
pl.glide("kettle.wav", far=3, near=0.2).save("results/glide")   # a moving source
y = pl.render("kettle.wav", distance=0.3)     # one position as a numpy array
```

The same ladder from the terminal:

```bash
proximal-ladder ladder kettle.wav --distances 3 1 0.3 --set 'room.dimensions=[8, 5, 3]' \
    --set 'room.listener=[2, 2.5, 1.2]' --set room.rt60=0.5 --set head.radius=0.08
proximal-ladder room --set room.absorption=0.2 --set room.walls.floor=carpet_cotton --measure
proximal-ladder docs guide                  # the full guide
```

`proximal-ladder room` shows a room's reverberation before you render
anything.

## What you can set

Any setting you leave out keeps its default. `help(pl.ladder)` lists the
common ones, and `proximal-ladder docs parameters` gives the full
reference.

| setting | meaning |
|---|---|
| `distances`, `azimuths` | where the source is: metres from the head centre, degrees (0 = ahead, +90 = the ear's side) |
| `room_size`, `head_position` | the room's [length, width, height], and the head's [x, y, ear height], in metres |
| `rt60`, `absorption`, `walls` | the room's reverberation time; or absorption for all surfaces or per wall, as a coefficient, 7 octave bands, or one of 90 named materials (`proximal-ladder materials`) |
| `head_radius`, `ear_azimuth` | the head |
| `mic` | `"cardioid"` or `"fig8"` |
| `lufs`, `loudness` | loudness target (−28 LUFS), or `"relative"` to keep level as a distance cue |
| `start`, `duration`, `tail` | which part of the file, and how much reverberation to keep at the end |
| `arm` | `"combined"` (room, head, mic), `"reception"` (room, head) or `"capture"` (mic only) |

Anything else uses the section and name joined by a double underscore,
e.g. `room__max_order=50`. Alternatively, pass a parameter file with
`config=` (on the terminal: `-c`); `proximal-ladder config` writes one
with every setting explained. Typos are refused with a suggestion.

## The model

| stage | model |
|---|---|
| **Room** | image-source shoebox model (Allen & Berkley 1979, via pyroomacoustics), with air absorption |
| **Head** | exact rigid-sphere transfer function at the near ear (Duda & Martens 1998), below ~4 kHz |
| **Microphone** | proximity effect of a first-order gradient microphone |
| **Loudness** | ITU-R BS.1770, −28 LUFS, true peak ≤ −1 dBTP; ISO 532-1 sones reported |

**Glides** move the source continuously between two distances. Each comes
in three versions:

- **A:** only the loudness changes.
- **B:** only the spatial cues change.
- **C:** both change, as the physics predicts.

The equations are in `proximal-ladder docs model`.

**Limits:**

- Mono output: one ear, not binaural.
- Shoebox room without scattering. With absorption on only a few surfaces the decay is longer than Sabine predicts, which `room --measure` shows.
- The head model is a rigid sphere.
- Sounds must be at least 0.4 s long.
- Renders are relative to the recording's own perspective.

## Requirements

- **Python:** 3.9 or newer.
- **Dependencies:** numpy, scipy, soundfile, pyloudnorm, mosqito, pyroomacoustics, matplotlib (installed automatically).
- **ffmpeg (optional):** adds mp3, m4a and aac input, and mp3 output.

## Credits

proximal-ladder implements published methods and builds on open-source
libraries. Please also cite the works behind the parts you rely on.

- **Room:** the image-source method of Allen & Berkley (1979, *JASA*
  65:943), computed with pyroomacoustics (Scheibler, Bezzam & Dokmanić
  2018, *ICASSP*; MIT licence, © EPFL-LCAV). The absorption materials come
  from its database.
- **Head:** the rigid-sphere solution of Rabinowitz, Maxwell, Shao & Wei
  (1993, *Presence* 2:125), evaluated as in Duda & Martens (1998, *JASA*
  104:3048). The validity band follows Brungart & Rabinowitz (1999,
  *JASA* 106:1465).
- **Microphone:** first-order gradient-microphone theory (Olson 1957;
  Beranek & Mellow 2012; Eargle 2004).
- **Loudness:** ITU-R BS.1770-4, measured with pyloudnorm (Steinmetz &
  Reiss 2021; MIT licence), whose K-weighting filter design is used at
  sample rates other than 48 kHz. ISO 532-1 loudness comes from MoSQITo
  (Apache 2.0).
- **Also:** NumPy, SciPy, python-soundfile/libsndfile, Matplotlib, and
  ffmpeg (optional, called as a separate program).

## Licence

MIT © 2026 Rishi Yildiz. The licence covers this package's own code; the
libraries above keep their own licences.
