SofaMyRoom from Python
The pysofamyroom package drives the SofaMyRoom library in-process. It
takes the same parameters, with the same names and defaults, as the setup
file of the command line tool and the MATLAB structure of the MEX
interface, and it returns the same impulse responses: for a given setup
the three front ends produce identical samples.
Installation
pip install pysofamyroom
Wheels are published for Linux, macOS and Windows. Installing from the source distribution (or from a clone of the repository) compiles the C library and needs a C compiler, CMake and the zlib development files; the other third-party code is included.
Requires Python 3.10 or later, numpy, scipy and sofar (used to read the SOFA source grid when generating VAST datasets).
Quick start
import pysofamyroom as smr
setup = smr.Setup(
room=smr.Room(dimension=(10, 7, 4), humidity=0.42, temperature=20),
options=smr.Options(fs=44100, responseduration=1.25,
simulatediffuse=True, seed=1),
sources=[smr.Source((8, 2.5, 1.6), (180, 0, 0), "subcardioid")],
receivers=[smr.Receiver.sofa("MIT_KEMAR_normal_pinna.sofa",
(3, 5, 1.2), interp=True)],
)
brirs = smr.simulate(setup)
brir = brirs[0][0] # source 0, receiver 0: (n_samples, 2)
brirs[s][r] holds the response of source s at receiver r as a
float64 array of shape (n_samples, n_channels), sampled at
setup.options.fs, which is what the MEX file returns in its cell array.
Write it to disk with scipy.io.wavfile.write or soundfile.
Absorption and diffusion are (6, n_bands) arrays, one row per surface
in the order x = 0, x = Lx, y = 0, y = Ly, floor, ceiling
(pysofamyroom.SURFACES), one column per entry of
Surface.frequency. A 1-D array is applied to all six surfaces:
surface = smr.Surface(frequency=[125, 250, 500, 1000, 2000, 4000, 8000],
absorption=[0.02, 0.06, 0.14, 0.37, 0.6, 0.65, 0.65],
diffusion=0.1 * np.ones(7))
room = smr.Room((7.1, 5.1, 3.0), surface=surface)
Setup files
Existing setup files are read and written unchanged, so a room described for the command line tool can be reused and modified in Python:
setup = smr.read_setup("data/sampleroomsetup.txt")
setup.sources[0].location = (2.0, 3.5, 1.6)
setup.options.seed = 42
brirs = setup.simulate()
setup.save("my_room.txt") # for the command line tool
smr.simulate() also accepts the path of a setup file or its text.
Relative paths inside a setup (for example the SOFA file) are resolved
against the current working directory, as in the command line tool;
pysofamyroom.Receiver.sofa() stores absolute, quoted paths so that
directories with spaces work.
Messages, errors and parallelism
The simulator’s progress messages (options.verbose) are written to
sys.stdout; redirect them with pysofamyroom.set_log() or
silence them with set_log(lambda text: None). An invalid setup or a
missing SOFA file raises pysofamyroom.SofaMyRoomError with the
simulator’s message.
The C library is single-threaded and keeps the loaded SOFA files in a
cache between calls (drop it with pysofamyroom.clear_sensors()).
Calls from several threads are serialised; to simulate many rooms in
parallel use processes, for example concurrent.futures
.ProcessPoolExecutor.
VAST datasets
pysofamyroom.vast is a port of matlab/VAST/VASTGeneration.m.
For every RoomCondition the sources are placed
on the measurement grid of the SOFA file, centred on the receiver and
clipped to the room, one response is simulated per grid point and the
responses of all conditions are stacked in one
VAST object together with the room, receiver
and source annotations described in SofaMyRoom - VAST:
from pysofamyroom import vast
conditions = [
vast.RoomCondition("living room", room,
receiver_location=(1.5, 1.5, 1.5)),
vast.RoomCondition("living room, turned", room,
receiver_location=(1.5, 1.5, 1.5), receiver_yaw=90),
vast.RoomCondition("hall", smr.Room((12, 9, 5), surface=surface),
receiver_location=(4, 3, 1.5)),
]
dataset = vast.generate(conditions, "MIT_KEMAR_normal_pinna.sofa",
fs=16000, duration=1.25)
dataset.rir_left.shape # (20000, n_responses)
dataset.save("vast_kemar.mat") # loadable in MATLAB: load('vast_kemar.mat')
workers=4 simulates the chunks of source positions in four processes;
the dataset is the same whatever the number of workers (in a script, keep
the call under if __name__ == "__main__": as for any multiprocessing).
The simulation runs at the sampling frequency of the HRTFs and the stored
responses are resampled to fs; vast.rt60() gives the
Norris-Eyring reverberation time per band used for the annotation. The
complete example is examples/vast_demo.py.
Correspondence with the other front ends
Setup file / MATLAB |
Python |
Notes |
|---|---|---|
|
|
|
|
|
unknown fields are kept in |
|
|
|
|
|
|
|
|
arrays instead of WAVE files |
|
|
same |
|
|
several rooms per dataset |