How to use the model¶
BayesianListener simulates a listener
behavior in a sound localisation task. This class is a stateful object
that holds three things: a template, a target, and noise parameters.
The template is the listener’s internal model of the acoustic world represented as auditory features extracted from their own HRTF and interpolated onto a quasi-uniform spherical grid. It is expensive to compute and rarely changes.
The target contains the auditory features of a single or multiple binaural stimuli to be localised. It can be swapped cheaply to change the sound source or test a non-individual HRTF.
The noise parameters (e.g.
sigma_spectral,sigma_prior) control perceputal and behavioral uncertainties and can be updated between calls.
localise is the one-call shortcut.
This design lets you swap just the target without recomputing everything.
If you want more control on the model internal workings,
then infer always operates on whatever
template and target are currently stored.
To give an idea of what this model can do, this guide shows four typical usage patterns.
Each builds on the same BayesianListener API; the differences are in
which features are computed and how target and template are combined
before calling infer.
Workflow |
When to use |
|---|---|
Simulate responses for a listener’s localising with their own HRTF. |
|
Simulate localisation with a non-individual HRTF. |
|
Track moving sound source over time. |
|
Update noise parameters. |
Individual¶
The simplest case: one listener, one HRTF.
localise handles feature
extraction, template interpolation, Bayesian inference, and motor noise in
a single call.
import pyfar as pf
bl = BayesianListener(sofa_path)
frontal = pf.Coordinates.from_spherical_elevation(0, 0, 1)
estimates = bl.localise(directions=frontal, repetitions=1)
lateral_deg = np.rad2deg(estimates.lateral.squeeze())
polar_deg = np.rad2deg(estimates.polar.squeeze())
print(f"\nFrontal estimate — lateral: {lateral_deg:.1f}°, polar: {polar_deg:.1f}°")
Results are cached on disk after the first call, so subsequent calls to
compute_template return
immediately.
Non-individual HRTF¶
Simulate how a listener performs when fitted with a non-individual HRTF. The individual listener’s template is retained; only the target (the stimulus representation) is replaced with the foreign HRTF.
import pyfar as pf
from bayesian_listener.metrics import angular_error
frontal = pf.Coordinates.from_spherical_elevation(0, 0, 1)
# Individual condition: P0001 template with P0001 target
repetitions = 50
bl_ind = BayesianListener(sofa_path)
est_ind = bl_ind.localise(directions=frontal, repetitions=repetitions, seed=0)
# Non-individual condition: P0001 template with P0002 target
foreign = BayesianListener(sofa_path_non_individual)
foreign.compute_target()
bl_non = BayesianListener(sofa_path)
est_non = bl_non.localise(target=foreign.target,
directions=frontal, repetitions=repetitions, seed=0)
err_ind = np.rad2deg(angular_error(frontal.cartesian, est_ind.cartesian.squeeze())[0])
err_non = np.rad2deg(angular_error(frontal.cartesian, est_non.cartesian.squeeze())[0])
print(f"\nFrontal great-circle error — individual: {err_ind:.1f}°, "
f"non-individual: {err_non:.1f}°, "
f"difference: {err_non - err_ind:.1f}°")
localise passes target to
infer, which compares the foreign
HRTF features against the individual template grid, modelling the
template-mismatch scenario.
Dynamic (loop over targets)¶
When comparing multiple stimulus conditions against the same template —
e.g. different HRTFs or source sets — compute the template once and swap
only the target inside the loop. Each target already contains
features for all source directions in its SOFA file, so a single swap
covers the full set of stimuli for that condition. This pattern is the
basis of the FrAMBI framework [barumerli2025], which uses repeated
inference over a sequence of conditions to model dynamic auditory tasks.
[llado2024] applied this approach to predict how headphone
HRTFs affect the time to localise a target in an auditory-guided visual
search task.
A moving source can be modelled by feeding the posterior of each step as
the prior for the next. The template stays fixed; only the single-direction
target changes at each step. With one repetition and store_posterior=True,
infer returns an array of shape
(1, 1, n_templates) — squeezed to (n_templates,) it is a valid prior
for the following call.
import numpy as np
bl = BayesianListener(sofa_path)
bl.compute_template()
bl.compute_target()
# Select directions on the horizontal plane, azimuth 0–90°
sph = bl.target.coords.spherical_elevation
azi_deg = np.rad2deg(sph[:, 0])
ele_deg = np.rad2deg(sph[:, 1])
mask = (ele_deg == 0) & (azi_deg >= 0) & (azi_deg <= 90)
# Build trajectory: one single-direction target per step, sorted by azimuth
trajectory_indices = np.where(mask)[0][np.argsort(azi_deg[mask])]
trajectory = [bl.target[i] for i in trajectory_indices]
actual_azimuths = azi_deg[trajectory_indices]
prior = "horizontal" # initialise with the default elevation prior
estimates = []
for step_target in trajectory:
bl.target = step_target
posterior = bl.infer(repetitions=1, prior=prior, store_posterior=True)
estimates.append(bl.estimate(posterior))
# squeeze (1, 1, n_templates) → (n_templates,) and convert to linear domain
prior = np.exp(posterior.squeeze())
print("\nTrajectory (actual → estimated azimuth):")
for actual, est in zip(actual_azimuths, estimates):
est_azi = np.rad2deg(est.spherical_elevation.squeeze()[0])
print(f" {actual:5.1f}° → {est_azi:5.1f}°")
Setting uncertainty parameters¶
The default parameter values are group averages from Barumerli et al. (2023)
and are a reasonable starting point, but individual listeners differ
substantially in their perceptual uncertainties. For accurate per-listener
simulation, set parameters explicitly before calling
localise or
infer.
Each parameter has a specific physical role in the static localisation task:
Parameter |
Default |
What it controls |
|---|---|---|
10.4 dB |
Reliability of monaural spectral cues (elevation, front/back). Higher values flatten the elevation response and increase front-back reversals. |
|
69.0 deg |
Width of the elevation prior. Lower values pull responses towards the horizontal plane regardless of the stimulus. |
|
23.31 |
Concentration of the motor-noise distribution. Lower values produce more scattered pointing responses independent of sensory processing. |
|
0.569 |
ITD perceptual noise (lateral localisation). Fixed at the literature value in most analyses — poorly identifiable from spatial data alone. |
|
1.0 dB |
ILD perceptual noise (lateral localisation). Fixed at the literature value in most analyses. |
Note
sigma_itd and
sigma_ild cannot be
separated from kappa_motor
along the lateral dimension and should be left at their defaults unless
you have strong prior justification. See Noise parameters
for the identifiability analysis.
All parameters can be set at construction time or updated in place:
import pyfar as pf
from bayesian_listener.metrics import localization_error
frontal = pf.Coordinates.from_spherical_elevation(0, 0, 1)
# Normal parameters with non-individual target
bl = BayesianListener(sofa_path,
sigma_spectral=8.2,
sigma_prior=45.0,
kappa_motor=30.0)
est_normal = bl.localise(directions=frontal, repetitions=50, seed=0)
# Impaired hearing (higher spectral uncertainty)
bl.sigma_spectral = 15.0
est_impaired = bl.localise(directions=frontal, repetitions=50, seed=0)
err_normal = localization_error(
frontal, est_normal, metric='angular_error', degrees=True)
err_impaired = localization_error(
frontal, est_impaired, metric='angular_error', degrees=True)
print(f"\nNon-individual frontal error — normal: {err_normal:.1f}°, "
f"impaired: {err_impaired:.1f}°, "
f"difference: {err_impaired - err_normal:.1f}°")
If you have measured behavioural responses from a listener, individual parameters can be estimated objectively via maximum likelihood optimisation rather than set by hand. See Fit the Model to Your Own HRTF for the two-stage fitting procedure.
See also¶
BayesianListener— full API reference.Simulate Localization Responses — metrics and response analysis.
Fit the Model to Your Own HRTF — estimating noise parameters from behavioural data.
References¶
R. Barumerli and P. Majdak, “FrAMBI: A Software Framework for Auditory Modeling Based on Bayesian Inference,” Neuroinformatics, vol. 23, no. 2, 2025. https://doi.org/10.1007/s12021-024-09702-5
P. Lladó, R. Barumerli, R. Baumgartner, and P. Majdak, “Predicting the effect of headphones on the time to localize a target in an auditory-guided visual search task,” Frontiers in Virtual Reality, vol. 5, 2024. https://doi.org/10.3389/frvir.2024.1359987