Open an interactive online version by clicking the badge Binder badge or download the notebook.

Room Simulation: Hybrid Simulation#

Fabian Brinkmann
Audio Communication Group, Technische Universität Berlin
Contact: fabian.brinkmann@tu-berlin.de

This notebook shows how to combine binaural room impulse responses (BRIRs) simulated in previous tasks using the image source model (ISM) and decaying noise. I recommend to complete these tasks before and use the BRIRs you simulated yourself. If you chose not to, this notebook also uses BRIRs that I simulated by default.

Duration: 60-120 Minutes

Requirements: Basic knowledge of HRTFs, digital signal processing and SOFA-Files

References
[1] F. Brinkmann, V. Erbes, and S. Weinzierl, “Extending the closed form image source model for source directivity,” in Fortschritte der Akustik – DAGA 2018, Munich, Germany, Mar. 2018, pp. 1298–1301. doi: 10.14279/depositonce-8681
[2] C. Borß and R. Martin, “An improved parametric model for perception-based design of virtual acoustics,” in AES 35th International Conference, London, UK, Feb. 2009.
[3] Lindau, A., Kosanke, L., & Weinzierl, S. (2012). Perceptual evaluation of model- and signal-based predictors of the mixing time in binaural room impulse responses. Journal of the Audio Engineering Society, 60(11), 887–898.
Dependencies
pip install pyfar>=0.8 sofar pooch nbgrader ipykernel watermark
[ ]:
import pyfar as pf
import sofar as sf
import numpy as np
import matplotlib.pyplot as plt
import pooch
import os
from IPython.display import Audio, display
%matplotlib inline

# this uses the pyfar default plot style for all plots
pf.plot.use()

The below downloads my version of the BRIRs computed in Room Simulation: Image Source Model for a Shoebox Room and Room Simulation: Late Reverb from Decaying Noise.

If you are using the BRIRs that you simulated your self - as recommended - you can later compare your results with the downloaded data.

[ ]:
# adjust this path to your needs. Using `None` will download the file to your
# system cash.
path = None

# Leave this as it is: This is the URL from which the data will be downloaded
# and a hash for checking if the download worked.
url = 'https://github.com/pyfar/files/raw/refs/heads/main/education/VAR_TUB/FABIAN_CTF_measured_inverted_smoothed.sofa?download='
hash = '68d79bf54ba8e0d7732bf14c525ac20404e751bba26a3e674036c53b71f70bfb'
file_ctf_inverse = pooch.retrieve(
    url, hash, fname='FABIAN_CTF_measured_inverted_smoothed.sofa', path=path)

url = 'https://github.com/pyfar/files/raw/refs/heads/main/education/VAR_TUB/brir_rt-0.5_volume-400.0.wav?download='
hash = '8155bbfb5b3731ebaf6a30c71c18000cdc6170ef58838db52ef1b235ba0d4e81'
brir_ism = pooch.retrieve(
    url, hash, fname='brir_rt-0.5_volume-400.0.wav', path=path)

url = 'https://github.com/pyfar/files/raw/refs/heads/main/education/VAR_TUB/binaural_late_reverb_rt-0.5.wav?download='
hash = '9264bf12a8e2e5e06fea9e8073df8ba11bb3046ddc268fab59ca0ebc763b7302'
brir_decaying_noise = pooch.retrieve(
    url, hash, fname='binaural_late_reverb_rt-0.5.wav', path=path)

Step 1: Load and inspect BRIRs#

Load and inspect the BRIR from the image source model and the binaural late reverb from the decaying noise assignment.

If you have already completed theses assignments you can continue using your own solutions.

[ ]:
# load ISM BRIR used for the direct sound and early reflections
# (path saved in `brir_ism` if you are using  the default data)
# YOUR CODE HERE
raise NotImplementedError()

# load decaying noise BRIR used for the late reverberation
# (path saved in `brir_decaying_noise` if you are using  the default data)
# YOUR CODE HERE
raise NotImplementedError()

# YOUR CODE HERE
raise NotImplementedError()

Step 2: Combination#

The following steps are required to combine the early and late part

a) Adjust delays: both IRs should start at the same time

b) Compute transition time: early and late part can be cross-faded at the perceptual mixing time

c) Adjust level: both IRs should have the same level around the transition time

d) Cross-fade: Cross-fade and combine the early and late part around the transition time.

e) Diffuse field filter: Apple inverse diffuse field transfer function to early part as an approximation for a headphone compensation filter.

2a: Adjust delays#

Detect the onset (start) of the early IR and delay the late IR accordingly

[ ]:
# detect onset in samples
# YOUR CODE HERE
raise NotImplementedError()

# delayed late reverberation
# YOUR CODE HERE
raise NotImplementedError()

# plot the resulting signals
# YOUR CODE HERE
raise NotImplementedError()

2b: Compute transition time#

We use a simple approximation for the perceptual mixing time, that is the time after which which we can not perceive any directional components in the BRIR. It is given by

\(t_\text{mix}=0.58 \sqrt{V} + 21.2\) [ms]

with the room volume \(V\) (cf. Lindau et al. (2012) J. Audio Eng. Soc. 60(1):887-898).

If you are using the default BRIRs, you can use \(V=400\,\mathrm{m}^3\).

[ ]:
# compute the mixing time in milliseconds
# YOUR CODE HERE
raise NotImplementedError()

# compute the mixing time in samples
# NOTE: The mixing time is defined with respect to the onset time of the BRIR.
#       You have to take this into account.
# YOUR CODE HERE
raise NotImplementedError()

2c: Adjust level#

Ideally the early and late reverberation have an identical level and decay around the mixing time. We cant match the latter perfectly, because the early part has a frequency independent decay and the late part a frequency dependent decay.

For simplicity, we adjust the level of the late reverberation by hand until we are satisfied with the match. This could also be done automatically based on the energy of both parts, for example.

[ ]:
# adjust the level of the late reverb by hand and plot the result
# YOUR CODE HERE
raise NotImplementedError()

2d: Cross-fade and combine#

Design a cross fade around the perceptual mixing time using sqaured sine and cosine ramps and pf.dsp.time_window.

[ ]:
# pad early and late part to the same length
# (makes it easier to check and apply the cross-fades)
# YOUR CODE HERE
raise NotImplementedError()

# define the duration of the cross-fade in samples
# YOUR CODE HERE
raise NotImplementedError()

# design the cross-fade windows (don't apply them yet)
# YOUR CODE HERE
raise NotImplementedError()

# apply windows to BRIRs after taking their square root
# (using the square root makes the windows energy preserving)
# YOUR CODE HERE
raise NotImplementedError()

# combine (add) early and late reverberation
# YOUR CODE HERE
raise NotImplementedError()

# Generate a 3 by one subplot showing
# - the windows and summed windows in the first subplot
# - the windowed BRIRs in the second subplot
# - the combined BRIR in the third subplot
# YOUR CODE HERE
raise NotImplementedError()

2e: Diffuse field filter#

Load and apply the inverse diffuse field transfer function from the FABIAN HRTF database as a simple alternative to a headphone compensation filter.

[ ]:
# The filename of the inverse DFTF is stored in the variable `file_ctf_inverse`
# YOUR CODE HERE
raise NotImplementedError()

# convolve all BRIRs with the inverse DFTF
# YOUR CODE HERE
raise NotImplementedError()

Step 3: Auralize#

[ ]:
# load an audio content of you choice and generate a player for it
content = pf.signals.files.drums(hybrid.sampling_rate)

content_player = Audio(content.time, rate=hybrid.sampling_rate)
display('Anechoic audio content')
display(content_player)

# Auralize one channel of the late reverberation. This equals a simulated
# isotropic RIR of a single-channel, omni-directional receiver.
# YOUR CODE HERE
raise NotImplementedError()

# Auralize BRIR from image source model
# YOUR CODE HERE
raise NotImplementedError()

# Auralize BRIR from decaying noise
# YOUR CODE HERE
raise NotImplementedError()

# Auralize Hybrid BRIR
# YOUR CODE HERE
raise NotImplementedError()

License notice#

This notebook is licensed under CC BY 4.0

Watermark#

The following watermark might help others to install specific package versions that might be required to run the notebook.

[1]:
%load_ext watermark
%watermark -v -m -p numpy,pyfar,sofar,nbgrader,pooch,watermark
Python implementation: CPython
Python version       : 3.14.7
IPython version      : 9.17.1

numpy    : 2.4.6
pyfar    : 0.8.1
sofar    : 1.3.0
nbgrader : 0.9.5
pooch    : 1.9.0
watermark: 2.6.0

Compiler    : Clang 20.1.8
OS          : Darwin
Release     : 25.5.0
Machine     : arm64
Processor   : arm
CPU cores   : 8
Architecture: 64bit