Skip to content

API reference

Every public class and function is importable from compas_acoustics.

Scene and model

Scene

Scene(name: str, description: str = '', sampling_rate: int = None)

Bases: _PackageDtype, Data

The acoustic model of a room: its surfaces with materials, sources, receivers, and the impulse responses simulated for them.

Parameters:

Name Type Description Default
name str
required
description str
''
sampling_rate int

The sample rate every simulation method runs at; default 48000.

None

Examples:

>>> import compas_acoustics as ca
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> len(scene.acoustic_objects), len(scene.sources), len(scene.receivers)
(6, 1, 1)
>>> scene.sampling_rate
48000

acoustic_materials property writable

acoustic_materials

The unique acoustic materials of the scene, keyed by name (as scene_materials).

sampling_rate property writable

sampling_rate: int

The sample rate simulate runs every method at.

scene_materials property

scene_materials

The unique acoustic materials used in the scene, keyed by material name.

sources property

sources

The scene's sources, then those attached to its acoustic objects.

add_impulse_response

add_impulse_response(source: Source, receiver: Receiver, ir: ImpulseResponse, attribute: str = 'data') -> None

Store ir on the source-receiver edge under attribute.

Each method stores its responses under its name ('mono', 'binaural', 'ambisonics'); 'data' holds the latest mono response.

impulse_responses

impulse_responses(attribute: str = 'data') -> dict

Every response stored under attribute, keyed '<receiver guid>_<source guid>'.

populate_ir_graph

populate_ir_graph(impulse_responses: Union[dict, Iterable[ImpulseResponse]], attribute: str = 'data') -> None

Replace every response stored under attribute; other attributes are kept.

simulate

simulate(method) -> dict

Run a simulation method at sampling_rate and store its responses on the IR graph under method.name.

A mono method's responses also go under 'data', which analysis and auralisation read by default.

Parameters:

Name Type Description Default
method [`SimulationMethod`][compas_acoustics.simulation_methods.base.SimulationMethod]

E.g. Mono(ray_tracing=True), Binaural() or Ambisonics().

required

Returns:

Type Description
Dict[str, ImpulseResponse]

One per source-receiver pair inside the room, keyed '<receiver guid>_<source guid>'.

simulation_methods

simulation_methods() -> list

The names of the simulation methods whose responses are on the IR graph, 'data' aside.

AcousticObject

AcousticObject(acoustic_geometry: Mesh, material: AcousticMaterial, x: float = 0, y: float = 0, z: float = 0, name: str = None, role: str = 'boundary', group: str = None)

Bases: _PackageDtype, Data

A surface of the scene with its material.

Parameters:

Name Type Description Default
acoustic_geometry [`compas.datastructures.Mesh`][compas.datastructures.Mesh]
required
material [`compas_acoustics.AcousticMaterial`][compas_acoustics.materials.AcousticMaterial]
required
x float

The geometry's reference point.

0
y float

The geometry's reference point.

0
z float

The geometry's reference point.

0
name str

Default: the class name.

None
role str

'boundary' (part of the closed room, the default) or 'obstacle' (free-standing inside it).

'boundary'
group str

The OBJ group the object was read from, for assign_roles_by_group and assign_materials_by_group. Not serialised.

None

assign_materials_by_group staticmethod

assign_materials_by_group(objects, materials)

Set the material of every object read from the named OBJ groups.

Parameters:

Name Type Description Default
objects list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

From from_obj, of one or several files.

required
materials dict

AcousticMaterial by group name; other groups keep theirs. An unknown name raises ValueError.

required

Returns:

Type Description
list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

objects.

Examples:

>>> objects = AcousticObject.from_obj("office.obj")
>>> AcousticObject.assign_materials_by_group(objects, {"chairs": upholstery, "glass": glass})

assign_roles_by_group staticmethod

assign_roles_by_group(objects, roles)

Set the role of every object read from the named OBJ groups.

Parameters:

Name Type Description Default
objects list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

From from_obj, of one or several files.

required
roles dict

'boundary' or 'obstacle' by group name. An unknown name raises ValueError.

required

Returns:

Type Description
list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

objects.

Examples:

>>> furniture = {"chairs": "obstacle", "tables": "obstacle"}
>>> objects = AcousticObject.from_obj("office.obj", materials)
>>> AcousticObject.assign_roles_by_group(objects, furniture)

from_obj classmethod

from_obj(filepath, materials=None, name=None)

One acoustic object per g group of a Wavefront OBJ file.

Faces before any g line are in 'default'; a file without g lines is grouped by its o lines. A group that recurs is merged into the first. Objects are named '<name>_<group>'. Coordinates are metres, not rounded (RoomGeometry welds them). Faces may use i, i/j, i//k or i/j/k indices, negative ones relative; faces with fewer than three distinct vertices are dropped. Every object is a boundary; mark furniture with assign_roles_by_group.

Parameters:

Name Type Description Default
filepath str
required
materials [`AcousticMaterial`][compas_acoustics.materials.AcousticMaterial] or dict

One material for every group, or one per group name as assign_materials_by_group takes them. A scene with an object without a material cannot be simulated.

None
name str

The prefix of the objects' names; defaults to the file name.

None

Returns:

Type Description
list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

AcousticObjectsLibrary

Factories of commonly used acoustic objects.

shoebox_room classmethod

shoebox_room(length: float, width: float, height: float, materials=None, sources: list = None, name: str = None)

A shoebox room as six surfaces, each its own AcousticObject.

Parameters:

Name Type Description Default
length float
required
width float
required
height float
required
materials dict or [`AcousticMaterial`][compas_acoustics.materials.AcousticMaterial]

One material for every surface, or one per surface name ('floor', 'left_wall', 'front_wall', 'right_wall', 'back_wall', 'ceiling'). Missing ones: absorption and scattering 0.1.

None
sources list

Sources attached to the floor.

None
name str

The prefix of the surfaces' names; default from the dimensions.

None

Returns:

Type Description
list of [`AcousticObject`][compas_acoustics.acoustic_object.AcousticObject]

Floor, right, front, left and back wall, ceiling. Facing +x from the centre, 'left_wall' is at y = width, 'right_wall' at y = 0 and 'front_wall' at x = length.

RoomGeometry dataclass

RoomGeometry(vertices: ndarray, faces: Tuple[Tuple[int, ...], ...], face_materials: Tuple[str, ...], face_names: Tuple[str, ...], face_roles: Tuple[str, ...], materials: Dict[str, SurfaceMaterial], is_closed: bool, open_edge: Optional[Tuple[Tuple[float, ...], Tuple[float, ...]]] = None, obstacle_faces: Tuple[Tuple[int, ...], ...] = (), obstacle_face_materials: Tuple[str, ...] = (), mergeable_faces: int = 0)

Every surface of a scene as one room: welded, oriented, materials on SYNTHESIS_BANDS.

Faces are listed boundary first, then the front and then the back copy of every obstacle face. is_closed says whether the boundary faces close a volume; open_edge holds the end points of the first edge that prevents it.

surface_area property

surface_area: float

Total area exposed to the air: Sabine's and Eyring's S, and the S of the mean free path 4 V / S.

surfaces property

surfaces: List[Tuple[float, SurfaceMaterial]]

(area, material) per material over the faces exposed to the air (see _exposed_faces).

volume property

volume: float

The air volume: the boundary's, less the volume each closed obstacle encloses. Defined only for a closed room.

area_normals

area_normals() -> ndarray

The unit normal of every exposed face scaled by its area, shape (n_exposed, 3); the boundary faces come first.

faces_with_materials

faces_with_materials() -> List[Tuple[ndarray, str]]

Every face as (vertices (k, 3), material name), obstacle copies included.

from_mesh classmethod

from_mesh(vertices, faces, face_materials, materials, obstacles=(), merge_coplanar: bool = False, weld_tolerance: float = 0.001) -> RoomGeometry

A room from shared vertices and faces; materials maps names to SurfaceMaterial, those in obstacles are obstacles.

from_scene classmethod

from_scene(scene, merge_coplanar: bool = True, weld_tolerance: float = 0.001) -> RoomGeometry

Collect, weld, orient and validate every surface of scene.

Boundary faces that close a volume are wound outward; otherwise they are kept as drawn, with a UserWarning. Obstacle faces are stored twice, OBSTACLE_OFFSET either side and wound opposite, since a wall reflects off one side only. merge_coplanar joins coplanar faces of one object and material into larger walls (a triangulated CAD mesh); False keeps them and warns where it could. weld_tolerance, in metres, is a CAD model's usual absolute tolerance.

is_inside

is_inside(point) -> bool

Whether point is in the air: inside a closed boundary (anywhere in an open scene), outside every closed obstacle.

Decided by generalised winding numbers over the drawn faces, so the obstacle copies play no part.

to_pra

to_pra(fs: int, max_order: int, ray_tracing: bool = False, air_absorption: bool = False, enclosure_only: bool = False, air: Optional[AirConditions] = None) -> Room

The pyroomacoustics room, one wall per face, materials resampled to its octave grid (nine bands at 48 kHz).

Bands above 8 kHz hold the 8 kHz value, where linear extrapolation could pass 1. A material without scattering scatters DEFAULT_SCATTERING. enclosure_only leaves the obstacles out. The room answers is_inside with is_inside. air (default AirConditions()) sets the speed of sound (Cramer) and, with air_absorption, the ISO 9613-1 absorption, in place of pyroomacoustics' own 343 m/s and absorption table.

without_obstacles

without_obstacles() -> RoomGeometry

The enclosure alone.

Source

Source(name: str, x: float, y: float, z: float, directivity: str = None, direction: Vector = None, sound_file: str = None)

Bases: _PackageDtype, Point

A sound source: position, directivity and facing, an optional sound file, and its level.

spl is the free-field level on its axis at 1 m per octave band, 125 Hz to 8 kHz, in dB re 20 uPa (the voice presets give the talker levels of ANSI S3.5-1997). On the package scale the direct sound is 1 / (4 pi r) with gain 1 on axis, so Lp = spl + G - 20 dB (Analyser.sound_pressure_level).

transform

transform(T)

Transform the source: its position and its direction.

Receiver

Receiver(name: str, x: float, y: float, z: float, direction: Vector = None, radius=0.5, up: Vector = None)

Bases: _PackageDtype, Point

A receiver with an orientation, the frame of its ambisonic responses: x is direction, y its left, z up.

Parameters:

Name Type Description Default
name str

Lowercase, without spaces.

required
x float
required
y float
required
z float
required
direction [`compas.geometry.Vector`][compas.geometry.Vector]

Where it faces; default +x.

None
radius float

In metres.

0.5
up [`compas.geometry.Vector`][compas.geometry.Vector]

Default +z; only its component orthogonal to direction is used.

None

frame property writable

frame

compas.geometry.Frame : The position and the axes of rotation; setting it sets direction and up.

rotation property

rotation

The orientation, a 3x3 proper rotation whose columns are the facing, left and up axes in world coordinates.

Its transpose takes a world vector to the receiver frame.

from_frame classmethod

from_frame(name: str, frame, radius=0.5)

A receiver at frame.point, facing frame.xaxis with frame.zaxis up.

transform

transform(T)

Transform the receiver: its position and its orientation.

AirConditions dataclass

AirConditions(temperature: float = 20.0, humidity: float = 50.0, pressure: float = 101325.0)

Air at a temperature (deg C), relative humidity (%) and static pressure (Pa), within Cramer's 0 to 30 deg C and 75 to 102 kPa.

speed_of_sound property

speed_of_sound: float

Speed of sound in m/s, after Cramer (1993).

air_attenuation

air_attenuation(frequencies) -> ndarray

Energy attenuation coefficient m per metre at frequencies in Hz (above 50 Hz), after ISO 9613-1.

Materials

AcousticMaterial

AcousticMaterial(name: str, absorption_coefficients, scattering_coefficients: list = None, transmission_coefficients: list = None, description: str = None, category=None, data_origin: str = None)

Bases: _PackageDtype, Data

Absorption, scattering and transmission coefficients per octave band of Defaults.DEFAULT_FREQUENCY_BANDS.

Parameters:

Name Type Description Default
name str
required
absorption_coefficients float or list of float

One per band, or one for every band.

required
scattering_coefficients float or list of float
None
transmission_coefficients float or list of float
None
description str
None
category [`MaterialCategory`][compas_acoustics.materials.MaterialCategory] or str

Default MaterialCategory.OTHER.

None
data_origin str

Default "custom".

None

slug property

slug

The name as the library's file name.

add_to_library

add_to_library()

Write the material to the library folder.

scattering_coefficients_from_depth

scattering_coefficients_from_depth(depth, s_max=0.8, c=DEFAULT_SPEED_OF_SOUND)

Set the scattering coefficients from a surface depth: s = s_max (1 - exp(-(k d)^2)) per band.

After Dalenbaeck (1995), Room acoustic prediction and auralization - how close can we get to reality?, ICA Trondheim; and Paetynen and Lokki (2010), Acta Acust. united Ac. 96(1).

Parameters:

Name Type Description Default
depth float

The surface's characteristic depth, in metres.

required
s_max float

Default 0.8.

0.8
c float

Speed of sound in m/s; default that of AirConditions().

DEFAULT_SPEED_OF_SOUND

Returns:

Type Description
list of float

MaterialCategory

Bases: str, Enum

Broad groups of acoustic materials, as the library is organised.

parse classmethod

parse(value)

Get a category from a member, its value ("Stone & Tiles") or its name ("STONE_AND_TILES").

MaterialsLibrary

MaterialsLibrary(library_path: str = None)

The materials of a folder of JSON files, one per material, looked up by name.

index property

index: dict

Material names to JSON file paths, read on first access.

material_names property

material_names: list

list : The names of the library's materials.

materials_by_category property

materials_by_category: dict

dict : Sorted material names per MaterialCategory that has any, in the categories' order.

path_from_material_name

path_from_material_name(material_name: str) -> str

The JSON file of a material, or where it would be stored when not in the library.

Simulation methods

Binaural and Ambisonics need Python 3.11 or newer.

SimulationMethod

Bases: ABC

A way to compute impulse responses. See the module docstring for the output contract.

compute abstractmethod

compute(context: SimulationContext) -> dict

One response per source-receiver pair, keyed '<receiver guid>_<source guid>'.

Mono

Mono(image_source_order: int = 3, ray_tracing: bool = True, air_absorption: bool = True, n_rays: Optional[int] = 20000, energy_thres: float = 1e-07, duration: Optional[float] = None, receiver_radius: float = 0.5, seed: Union[bool, int] = True, normalize: bool = True, air: Optional[AirConditions] = None)

Bases: SimulationMethod

Image sources up to image_source_order, and the stochastic ray tracer past it if ray_tracing.

Parameters:

Name Type Description Default
image_source_order int

Last specular order; with ray_tracing the ray tracer covers what follows. The search cost grows steeply with the order (image_source_search_seconds).

3
ray_tracing bool

Materials without scattering coefficients scatter 0.05 in every band.

True
air_absorption bool
True
n_rays int or None

Per source; None: a heuristic of at least 50000.

20000
energy_thres float

The product of its reflections in its least absorbed band at which a ray stops; the logged energy is that over the squared distance (Schroeder 2011, eq. 5.54), so the tail has decayed much further.

1e-7
duration float

Of the ray-traced responses, in seconds; default 1.5 times the longest Eyring reverberation time, within 0.5 to 12 s (10 s for a scene that closes no volume), with a warning at the cap. Without ray tracing the responses have their natural length.

None
receiver_radius float

In metres.

0.5
seed bool or int

True seeds with 42, an int with that value, False or None leaves the run unseeded (generation["seed"] = None).

True
normalize bool

Whether the responses are peak-normalized views.

True
air AirConditions

Its speed of sound and, with air_absorption, its ISO 9613-1 absorption; default AirConditions().

None
Notes

Ray-tracing settings (n_rays to seed) changed with ray_tracing=False raise ValueError.

Examples:

>>> import compas_acoustics as ca
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> responses = scene.simulate(ca.Mono(image_source_order=2, ray_tracing=False))
>>> ir = next(iter(responses.values()))
>>> ir.sampling_rate, ir.n_channels, ir.generation["method"]
(48000, 1, 'mono')

Ray tracing, on by default, adds the late reflections; the seed makes a run repeatable.

>>> responses = scene.simulate(ca.Mono(n_rays=2000, duration=0.5))
>>> ir = next(iter(responses.values()))
>>> ir.ir.shape, ir.generation["seed"]
((24000,), 42)

Binaural

Binaural(reference=None, hrtf=None, *, tail=None, image_source_order=3, n_slopes=1, decay_db=30.0, duration=None, directional_absorption=True, late_envelope=None, diffraction=False, air_absorption=None, seed=True, normalize=True, air=None, scattering=False)

Bases: _HybridMethod

Binaural room impulse responses: image sources, optional edge diffraction, a directional FDN.

The early field is the image sources up to image_source_order (and, with diffraction, edge diffraction). The last order drives a feedback delay network of 96 lines, one per direction, whose decay follows a target (Wendt et al. 2014), the count Kirsch et al. (2023) use offline. Every early path and delay line is filtered with the HRIR measured nearest its direction. Needs Python >= 3.11 and a closed room. Responses are ears left then right, stored under 'binaural'; generation["hrtf"] names the set.

Parameters:

Name Type Description Default
reference (str, dict, Mono or None)

A package-scale mono response per pair for the decay target and the late envelope: a method's name on the IR graph ("mono", "data"), a dict as Scene.simulate returns, or a Mono to run first.

None
hrtf str or path

"kemar" (None), the KEMAR set of the HRTF library, or a SOFA file.

None

Examples:

>>> import compas_acoustics as ca
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> ir = next(iter(scene.simulate(ca.Binaural(tail="eyring")).values()))
>>> ir.n_channels, ir.generation["hrtf"]
(2, 'KEMAR_FreeFieldComp_48kHz.sofa')

With a ray-traced reference the decay is fitted to a response you already have.

>>> reference = scene.simulate(ca.Mono(ray_tracing=True))
>>> responses = scene.simulate(ca.Binaural(reference="mono"))

Ambisonics

Ambisonics(reference=None, sh_order=1, normalization='SN3D', *, tail=None, image_source_order=3, n_slopes=1, decay_db=30.0, duration=None, directional_absorption=True, late_envelope=None, diffraction=False, air_absorption=None, seed=True, normalize=True, air=None, scattering=False)

Bases: _HybridMethod

Ambisonic room impulse responses: image sources, optional edge diffraction, a directional FDN.

As Binaural, with every early path encoded at its direction and every delay line projected from its direction at sh_order. Responses are ACN in normalization, stored under 'ambisonics'.

Parameters:

Name Type Description Default
reference (str, dict, Mono or None)

As Binaural.

None
sh_order int

The network has 96 delay lines up to third order (Kirsch et al. 2023), above that the smallest equal-area grid supporting the order. The input gain sqrt(num_delays) offsets the output's 1 / sqrt(num_delays), so the late level does not depend on the count.

1
normalization ('SN3D', 'N3D')

ACN channel order, W = 1 for a unit plane wave.

"SN3D"

Examples:

>>> import compas_acoustics as ca
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> ir = next(iter(scene.simulate(ca.Ambisonics(tail="eyring", sh_order=2)).values()))
>>> ir.n_channels, ir.ambisonic_order
(9, 2)

num_delays property

num_delays: int

96 up to third order, more above (default_num_delays).

Results and analysis

ImpulseResponse

ImpulseResponse(name: str, source: Source, receiver: Receiver, ir: ndarray, sampling_rate: int, normalized: bool = True, generation: dict = None, ambisonic_order: int = None, normalization: str = None, channel_order: str = None)

Bases: _PackageDtype, Data

An impulse response between a source and a receiver.

Stored at its raw level with its normalization factor, the peak of all channels. ir is peak-normalized by default; denormalized and denormalize_group give the raw level, which keeps distance and level differences between responses.

Parameters:

Name Type Description Default
name str
required
source Source
required
receiver Receiver
required
ir ndarray

The raw response, (samples,) or (samples, channels).

required
sampling_rate int
required
normalized bool

Whether ir is peak-normalized; default True.

True
generation dict

The method and its settings; written to the JSON metadata on export.

None
ambisonic_order int

None for a mono or binaural response.

None
normalization str

'sn3d' or 'n3d'.

None
channel_order str

'acn'.

None

format property

format

str : 'mono', 'binaural' (two channels, no ambisonic order) or 'ambisonic'.

ir property writable

ir

np.ndarray : Peak-normalized when normalized; setting it sets the raw response.

level_db property

level_db

float : The normalization factor in dB (20 log10 of the raw peak).

metadata property

metadata

dict : The response and how it was generated, as written next to it on export.

n_channels property

n_channels

int : The number of channels.

normalization_factor property

normalization_factor

float : Peak absolute value of the raw impulse response across all channels.

denormalize_group staticmethod

denormalize_group(impulse_responses)

Raw impulse responses scaled together so the loudest peaks at 1.

Parameters:

Name Type Description Default
impulse_responses list or dict of ImpulseResponse
required

Returns:

Type Description
list or dict of np.ndarray

In the container type of the input.

denormalized

denormalized()

A copy of the raw impulse response.

direct_sound_index

direct_sound_index(speed_of_sound: float = None) -> int

Sample of the direct sound.

On the package scale (generation['scale'] == 'package') the geometric one, distance / c, even when early ray-traced bins are louder or an obstacle blocks it; otherwise the largest sample of the omnidirectional channel.

from_wav classmethod

from_wav(filepath, source: Source = None, receiver: Receiver = None, normalized: bool = True)

Read a WAV written by to_wav, restoring the raw level from its JSON metadata.

Without metadata the WAV is taken as the raw response.

Parameters:

Name Type Description Default
filepath str
required
source (Source, Receiver)

Used instead of the ones in the metadata.

None
receiver (Source, Receiver)

Used instead of the ones in the metadata.

None
normalized bool

Whether ir is peak-normalized; default True.

True

Returns:

Type Description
ImpulseResponse

resampled

resampled(target_sampling_rate: int, method: str = 'poly') -> NDArray[float64]

ir resampled to target_sampling_rate along axis 0, by "poly" or "fft".

to_wav

to_wav(filepath)

Write the peak-normalized response as a 32-bit float WAV, and metadata as JSON beside it.

The JSON holds the normalization factor, from which from_wav restores the raw level.

Returns:

Type Description
tuple of Path

The WAV and JSON files.

Analyser

Analyser(ir)

Room acoustic parameters of an impulse response.

Parameters:

Name Type Description Default
ir (list, ndarray or ImpulseResponse)

Shaped (samples,) or (samples, channels). Only channel 0 (the omnidirectional component of an ambisonic response) is analysed. An ImpulseResponse is analysed at its raw (denormalized) level and sets sampling_rate.

required

Examples:

>>> import compas_acoustics as ca
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> responses = scene.simulate(ca.Mono(ray_tracing=True, n_rays=2000, duration=0.5))
>>> analyser = ca.Analyser(next(iter(responses.values())))
>>> results = analyser.analyse()
>>> results["freqs"]
[125, 250, 500, 1000, 2000, 4000, 8000]
>>> results["T30_overall"] > 0
True

analyse

analyse(ir=None) -> dict

Every parameter of _PARAM_NAMES per band and as '<name>_overall'.

The overall values of EDT, T20, T30, C50, C80, D50 and Ts average 500 and 1000 Hz (ISO 3382-1:2009 Table A.1); the others have their own.

center_time

center_time(impulse_response=None) -> dict

Centre time Ts per band, in seconds from the broadband direct sound (ISO 3382-1), of the zero-phase bands.

clarity_definition

clarity_definition(impulse_response=None) -> dict

C50, C80 and D50 per band (ISO 3382-1).

Early and late parts are cut from the broadband response before band filtering, so filter delay and ringing stay with the part they belong to.

direct_to_reverberant_ratio

direct_to_reverberant_ratio(impulse_response=None) -> dict

DRR per band and in 700-4000 Hz.

The energy of the first 5 ms from the direct sound over the energy after it, in dB. The window opens 1 ms early so that a band-limited arrival is held whole. 'DRR_overall' is theirs: a second-order Butterworth band-pass of 700-4000 Hz, since 5 ms does not resolve the low octaves. Per band the parts are cut before filtering, as for C50.

Returns:

Type Description
dict

'DRR' per band in frequency_bands, and 'DRR_overall'.

export_csv

export_csv(filepath: str, parameters=None)

Write the results of analyse to a CSV file.

Parameters:

Name Type Description Default
filepath str
required
parameters str or list of str

Default: all.

None

lateral_fraction

lateral_fraction(impulse_response=None) -> dict

Early lateral energy fractions J_LF and J_LFC per band (ISO 3382-1:2009 Annex A).

J_LF: the figure-of-eight energy 5-80 ms after the direct sound over the omnidirectional energy 0-80 ms. J_LFC takes |p_L p| for p_L^2 (Kleiner 1989), a cosine instead of a cosine-squared weighting of lateral arrivals (Bradley 1994). The figure-of-eight is the first-order dipole of an ambisonic response, horizontal and perpendicular to the source direction, which is the geometric one when the response carries its source and receiver, else the direct sound's first-order intensity. Windows are cut before band filtering, as for C80.

Parameters:

Name Type Description Default
impulse_response ImpulseResponse

An ambisonic response, ACN, SN3D or N3D. Default is the analyser's own.

None

Returns:

Type Description
dict

'freqs', 'LF' and 'LFC' per band in frequency_bands, and 'LF_overall' and 'LFC_overall', the mean of 125-1000 Hz. NaN for a response that is not ambisonic.

plot

plot(parameters=None)

Plot the results of analyse against frequency, one figure per parameter (default: all).

plot_frequency_response

plot_frequency_response(smooth=3, raw_alpha=0.3)

Plot the magnitude response, with a 1/smooth octave smoothed one over it unless smooth is None.

plot_rir

plot_rir()

Plot the impulse response against time.

print_table

print_table(parameters=None, show_bands=True, show_overall=False)

Print the results of analyse.

Parameters:

Name Type Description Default
parameters str or list of str

Default: all.

None
show_bands bool

Per-band values.

True
show_overall bool

Overall values.

False

reverberation_times

reverberation_times(impulse_response=None) -> dict

EDT, T20 and T30 per band (ISO 3382-1).

Schroeder (1965) backward integration of the zero-phase band (_band_from_onset), from the direct sound to the last non-zero sample, normalized to 0 dB at its start, then linear regression. No noise compensation, since a simulated response has no noise floor: truncate a measured response at its noise floor first.

sound_pressure_level

sound_pressure_level(source_spl=None, impulse_response=None, strength=None) -> dict

Sound pressure level per band from the source's level at 1 m and G.

Lp = spl + G - 20 dB: on the package scale G's reference, the free field at 10 m, is 20 dB below the free field at 1 m, where spl is given (compas_acoustics.Source.spl).

Parameters:

Name Type Description Default
source_spl float or list of float

The source's on-axis level at 1 m, per band of frequency_bands or one for all. Default: the spl of the response's source.

None
impulse_response ImpulseResponse

A package-scale response; default is the analyser's own.

None
strength dict

G as sound_strength returns it, when already computed.

None

Returns:

Type Description
dict

'freqs', 'SPL' per band in dB, 'SPL_overall' the A-weighted total in dB(A) and 'SPL_total' the unweighted one in dB. NaN, with a warning, without a level or off the package scale.

sound_strength

sound_strength(reference_ir=None, impulse_response=None) -> dict

Strength G per band (ISO 3382-1): the response's energy over that of the free field at 10 m.

Both responses at their raw level: ImpulseResponse objects (used denormalized) or raw arrays. The single number averages 500 and 1000 Hz.

Parameters:

Name Type Description Default
reference_ir ImpulseResponse or ndarray

The source's free-field response at 10 m, at the same sample rate. Default: the package-scale free field 1 / (4 pi 10), for a package-scale response.

None
impulse_response ImpulseResponse or ndarray

Default is ir.

None

Returns:

Type Description
dict

'freqs', 'G' per band in frequency_bands, and 'G_overall'.

speech_transmission_index

speech_transmission_index(impulse_response=None, snr=inf) -> dict

STI and the modulation transfer index per octave band (IEC 60268-16:2020, indirect method).

Responses shorter than the 1.6 s of Section 6.2 are zero-padded, which assumes a full decay. snr is the signal-to-noise ratio in dB, one value or one per octave band 125-8000 Hz.

Returns:

Type Description
dict

'mti' per octave band 125-8000 Hz and 'sti'; NaN where pyrato cannot compute them.

stage_support

stage_support(impulse_response=None) -> dict

ST_early and ST_late per band (Gade; ISO 3382-1:2009 Annex C), for source and receiver 1 m apart.

The energy 20-100 ms (ST_early) or 100-1000 ms (ST_late) after the direct sound over the energy of the first 10 ms, in dB; the windows are cut before band filtering. Single numbers average 250-2000 Hz.

Returns:

Type Description
dict

'freqs', 'ST_early' and 'ST_late' per band in frequency_bands, and 'ST_early_overall' and 'ST_late_overall'.

EnergyMap

EnergyMap(points, inside, bands, bin_size, energy, name=None)

Bases: _PackageDtype, Data

The energy of a scene's sources at many points, per octave band and time bin, in Pa^2 s / p0^2.

10 log10 of the sum over the bins is the sound pressure level in dB re 20 uPa (spl).

Parameters:

Name Type Description Default
points array - like

The points, (n, 3).

required
inside array - like

Whether each point is in the air of the room, (n,); the others hold no energy and read NaN.

required
bands array - like

The octave bands' centres in Hz, (b,).

required
bin_size float

The length of a time bin in seconds; bin k starts k * bin_size after the emission.

required
energy array - like

(n, b, bins).

required
name str
None

frames

frames(frame_s: float = 0.012)

The broadband level per point in frames of frame_s seconds, for an animation of the energy arriving.

A frame is a whole number of bins: frame_s is rounded up to one.

Returns:

Type Description
tuple of (np.ndarray, np.ndarray)

The frames' start times (k,), in seconds, and the levels (n, k) in dB: -inf before any energy arrives, NaN outside the room.

from_scene classmethod

from_scene(scene, points, source_index=None, n_rays=None, image_source_order=1, duration=None, air_absorption=True, seed=True, ray_spacing=1.0) -> 'EnergyMap'

The energy map of scene's sources at points.

Parameters:

Name Type Description Default
scene [`compas_acoustics.Scene`][compas_acoustics.scene.Scene]
required
points iterable of xyz

The points, in the room's coordinates.

required
source_index int

One source by its index in scene.sources; by default every source, their energies added.

None
n_rays int

Rays per source; by default map_rays of the room's volume and the response's length.

None
image_source_order int

Image sources up to this order for every point, the rays past it. At least 1: at order 0 the ray tracer logs the direct path too, which would count it twice.

1
duration float

Seconds after the emission; by default 1.5 times the longest Eyring reverberation time, as the Mono method.

None
air_absorption bool
True
seed bool or int

The rays' seed: True seeds with 42, an int with that value, False leaves them unseeded.

True
ray_spacing float

Default 1 m. The rays are traced to one receiver per cell of this size, the point of the cell nearest its centre, and their energy is interpolated to the other points; None traces them to every point. A run costs rays times receivers, the late energy varies slowly and a 0.5 m receiver sphere resolves nothing finer; the image sources stay exact at every point.

1.0
Notes

Points outside the room, or inside a closed obstacle, are left out with one UserWarning.

spl

spl() -> ndarray

The sound pressure level per point and band, (n, b), in dB; NaN outside the room.

spl_a

spl_a() -> ndarray

The A-weighted total of the bands per point, (n,), in dB(A) (IEC 61672-1 weights at the band centres).

regularized_fitness

regularized_fitness(values: list[float], target: float, k: float = 1.0) -> float

Mean absolute error to target plus k times the sample standard deviation; lower is better.

Parameters:

Name Type Description Default
values list of float
required
target float
required
k float

Weight of the standard deviation.

1.0

Returns:

Type Description
float

999 for no values.

Rays and reflection paths

Ray

Ray(points, source=None, source_index: Optional[int] = None, ray_index: Optional[int] = None, direction=None, distance_offset: float = 0.0, name: Optional[str] = None)

Bases: _PackageDtype, Polyline

A ray traced through a scene, the polyline [source_position, hit_1, hit_2, ...].

Times are distances at Defaults().DEFAULT_SPEED_OF_SOUND.

Parameters:

Name Type Description Default
points list
required
source [`compas_acoustics.Source`][compas_acoustics.source.Source]
None
source_index int

The source's index in the scene.

None
ray_index int

The ray's index among those of its source.

None
direction [`compas.geometry.Vector`][compas.geometry.Vector] or list

The initial direction; default that of the first segment.

None
distance_offset float

The distance travelled before points[0]: non-zero for a scattered ray born at a hit.

0.0
name str
None

cumulative_distances property

cumulative_distances: List[float]

The travelled distance from the ray origin at each vertex, including distance_offset.

reflection_order property

reflection_order: int

The number of segments (hops) of the ray.

segment_lengths property

segment_lengths: List[float]

The length of each segment of the ray.

total_distance property

total_distance: float

The total distance travelled by the ray, including distance_offset.

travel_time property

travel_time: float

The total travel time of the ray in milliseconds.

is_in_cone

is_in_cone(axis, half_angle_deg: float) -> bool

Whether the ray's direction is within half_angle_deg of axis, a cone with its apex at the origin.

up_to_distance

up_to_distance(distance: Optional[float]) -> Optional[Ray]

A copy of the ray cut at distance from the source (distance_offset included); None: all.

Returns None if the ray has not emerged yet at distance.

up_to_reflection_order

up_to_reflection_order(order: Optional[int]) -> Ray

A copy of the ray with its first order segments; None: all.

up_to_time

up_to_time(time_ms: Optional[float]) -> Optional[Ray]

A copy of the ray cut at time_ms milliseconds, as up_to_distance.

ScatteredRay

ScatteredRay(points, parent_ray_index: Optional[int] = None, energy: Optional[float] = None, origin_reflection_order: int = 0, **kwargs)

Bases: Ray

A scattered ray born at a reflection point of a specular ray.

Parameters:

Name Type Description Default
points list

From the reflection point it scattered from.

required
parent_ray_index int
None
energy float

The scattered energy it carries.

None
origin_reflection_order int

The parent's segments up to that reflection point.

0
**kwargs {}

reflection_order property

reflection_order: int

The specular reflection order of the originating hit point.

RayField

RayField(rays: Optional[List[Ray]] = None, name: Optional[str] = None)

Bases: _PackageDtype, Data

Traced rays, with filters and display.

Parameters:

Name Type Description Default
rays list of [`compas_acoustics.Ray`][compas_acoustics.rays.Ray]

The specular rays, each with its scattered rays in ray.scattered_reflections.

None
name str
None

duration_ms property

duration_ms: float

The largest travel time of the specular rays in milliseconds, 0.0 when empty.

scattered_rays property

scattered_rays: List[ScatteredRay]

All scattered rays across every ray in the field.

display_in_viewer

display_in_viewer(scene=None, reflection_order: Optional[int] = None, travel_time_ms: Optional[float] = None, show_specular: bool = True, show_scattered: bool = False, color=None, scattered_color=None, linewidth: float = 1.0, show: bool = True)

Display the rays, and the scene's geometry, sources and receivers when given, in a viewer.

reflection_order and travel_time_ms are those of truncated. With show=False the viewer is returned without being opened.

filter

filter(source=None, ray_index: Optional[Union[int, List[int]]] = None) -> RayField

A new field of the rays of source (object or index) and of ray_index (one or several).

from_scene classmethod

from_scene(scene, n_rays: int = 500, max_reflection_order: int = 10, direction=None, angle: Optional[float] = None, duration: Optional[float] = None, energy_thres: Optional[float] = None, sources=None, include_scattering: bool = False, scatter_density: int = 10, scatter_max_bounces: int = 1, scatter_band: Optional[int] = None, random_seed: Union[bool, int] = True)

Trace rays through the scene for display.

Each source emits with the density of its radiated energy, the square of its first-order pattern p + (1 - p) cos(theta) about its direction; with a cone (direction and angle) all n_rays go into the cone, with the same density. With include_scattering each hit spawns scattered rays, their number and spread set by the scattering coefficient.

Parameters:

Name Type Description Default
scene [`compas_acoustics.Scene`][compas_acoustics.scene.Scene]
required
n_rays int

Per source.

500
max_reflection_order int
10
direction [`compas.geometry.Vector`][compas.geometry.Vector] or list

The axis of a cone to emit into, apex at each source. Requires angle.

None
angle float

The cone's half angle in degrees. Requires direction.

None
duration float

In seconds: a ray stops at the first reflection past the distance sound travels in it.

None
energy_thres float

A ray stops when the energy its reflections leave it, by the band mean of each wall's absorption (and scattering, with include_scattering), falls below this.

None
sources list of [`compas_acoustics.Source`][compas_acoustics.source.Source]

Default: all of the scene's. source_index is the index in scene.sources.

None
include_scattering bool
False
scatter_density int

Scattered rays per hit at scattering coefficient 1.

10
scatter_max_bounces int

Reflections traced for each scattered ray.

1
scatter_band int

The band whose scattering coefficient is used; default the mean of all.

None
random_seed bool or int

True seeds with 42, an int with that value, False or None leaves the scattering unseeded.

True

Returns:

Type Description
[`compas_acoustics.RayField`][compas_acoustics.rays.RayField]

particles

particles(time_ms: float)

The position of every specular ray at time_ms (clamped to [0, duration_ms]), for a billiard view.

Returns:

Type Description
tuple of (list of [`compas.geometry.Point`][compas.geometry.Point], list of int, list of int)

The positions, their rays' ray_index and the reflections each has passed. A ray that has ended gives no particle.

truncated

truncated(reflection_order: Optional[int] = None, travel_time_ms: Optional[float] = None) -> RayField

A new field of the rays cut to their first reflection_order segments and to travel_time_ms.

SpecularPath

SpecularPath(points, order: int, walls: List[int], image, receiver_index: int, **kwargs)

Bases: Ray

An image-source path [source_position, hit_1, ..., hit_order, receiver_position].

Parameters:

Name Type Description Default
points list
required
order int

The number of reflections.

required
walls list of int

The wall of each reflection, in path order: indices into RoomGeometry.faces of RoomGeometry.from_scene(scene), obstacle copies included.

required
image [`compas.geometry.Point`][compas.geometry.Point] or list
required
receiver_index int

The receiver's index in the scene.

required
**kwargs {}

reflection_order property

reflection_order: int

The number of walls the path is reflected by: its last segment ends at the receiver, not a wall.

SpecularReflections

SpecularReflections(paths: Optional[List[SpecularPath]] = None, name: Optional[str] = None)

Bases: RayField

The image-source paths between sources and receivers: a RayField of SpecularPath.

Parameters:

Name Type Description Default
paths list of [`SpecularPath`][compas_acoustics.rays.SpecularPath]
None
name str
None

orders property

orders: List[int]

The sorted distinct reflection orders present.

paths property

The reflection paths, the field's rays.

filter

filter(source=None, ray_index: Optional[Union[int, List[int]]] = None, receiver_index: Optional[int] = None, order: Optional[Union[int, List[int]]] = None) -> SpecularReflections

As RayField.filter, and the paths to receiver_index of order (one or several).

from_scene classmethod

from_scene(scene, max_order: int = 3) -> SpecularReflections

The paths of the visible image sources, orders 0 to max_order, from every source to every receiver.

Only the last wall of each image is recorded, so each path is traced back from the receiver: through that wall to the parent image, and on through whichever wall leads back to the source. An image found twice (through an edge, from both wall orderings) is kept once. Sources and receivers outside the room are left out with a UserWarning.

Parameters:

Name Type Description Default
scene [`compas_acoustics.Scene`][compas_acoustics.scene.Scene]
required
max_order int
3

Returns:

Type Description
[`SpecularReflections`][compas_acoustics.rays.SpecularReflections]

Audio

Auraliser

Auraliser(name: str, impulse_responses, anechoic_sounds=None, description: str = None, sampling_rate: int = None, level: str = 'relative', headroom_db: float = -1.0)

Convolves impulse responses with their anechoic sounds and sums them.

The signal keeps the format of its responses (format): 'mono', 'binaural' (two ears), or 'ambisonic' (ACN/SN3D, (order + 1)**2 channels at the highest order present; N3D is converted). Responses of different formats cannot be mixed: there is no decoding. Responses are used at their raw level, so their level differences are kept; level sets the output level on export:

  • 'relative': one gain for a group of renders, the loudest peaking at headroom_db.
  • 'individual': each render peaks at headroom_db.
  • 'none': no gain; warns above 0 dBFS.

Parameters:

Name Type Description Default
name str

Lowercase, without spaces.

required
impulse_responses dict or list

A list of ImpulseResponse (ACN/SN3D when they state no convention), or a dict of ids to dicts with 'ir' ((samples,) or (samples, channels)), 'sampling_rate', 'ambisonic_order', 'channel_order' ('acn') and 'normalization' ('sn3d' or 'n3d'). All of one format.

required
anechoic_sounds dict or list

File paths, or a dict of ids to dicts with 'filepath'; paired with the responses by id when both are dicts with the same ids, else by position. Default: each response's source's sound_file.

None
description str
None
sampling_rate int

Responses and sounds are resampled to it. Default: that of the first response.

None
level str

'relative' (default), 'individual' or 'none'.

'relative'
headroom_db float

Target peak in dBFS for 'relative' and 'individual'; default -1.

-1.0

Examples:

>>> import compas_acoustics as ca
>>> sound = ca.Defaults().ANECHOIC_SOUNDFILES_LIBRARY_PATH + "/female_speech.wav"
>>> scene = ca.Scene("office")
>>> scene.add_acoustic_objects(ca.AcousticObjectsLibrary.shoebox_room(6, 5, 3, materials=ca.AcousticMaterial("wall", 0.2, 0.1)))
>>> scene.add_source(ca.Source("talker", 1.5, 1.5, 1.6, sound_file=sound))
>>> scene.add_receiver(ca.Receiver("listener", 4.0, 3.0, 1.2))
>>> responses = scene.simulate(ca.Mono(ray_tracing=True, n_rays=2000, duration=0.5))
>>> auraliser = ca.Auraliser("office", list(responses.values()))
>>> auraliser.format, auraliser.convolve().shape[1]
('mono', 1)

format property

format

str : 'mono', 'binaural' or 'ambisonic', the format of the impulse responses and of the signal.

impulse_responses property writable

impulse_responses

dict : Impulse responses as (samples, channels) arrays at sampling_rate, ambisonic ones in ACN/SN3D.

output_channels property

output_channels

int : Number of channels of the auralised signal: 1, 2, or (order + 1)**2 for ambisonics.

convolve

convolve()

Convolve each impulse response with its anechoic sound and sum, in format.

Responses that share a sound are summed first, so each sound is read and convolved once. Lower-order ambisonic responses are padded with zero channels to the highest order.

Returns:

Type Description
ndarray

The auralised signal, shaped (samples, output_channels).

export_convolved_sound

export_convolved_sound(filepath, convolved_sound=None, gain=None)

Write an auralised signal at its output level as a 32-bit float audio file.

Parameters:

Name Type Description Default
filepath str
required
convolved_sound ndarray

From convolve; default: convolve is called.

None
gain float

E.g. from render_gains for a group of renders. Default: from level and headroom_db for this signal alone.

None

Returns:

Type Description
ndarray

The written signal, shaped (samples, channels), as float32.

import_anechoic_sound

import_anechoic_sound(filepath, sampling_rate)

An audio file as a mono (samples,) array at sampling_rate, multichannel files mixed down.

peak_dbfs staticmethod

peak_dbfs(signal)

Peak absolute value of a signal in dBFS (-inf for silence).

render_gains staticmethod

render_gains(signals, level='relative', headroom_db=-1.0)

Gains that set the output level of a group of auralised signals.

Parameters:

Name Type Description Default
signals list of np.ndarray

From convolve, e.g. one per receiver or source.

required
level str

As level; default 'relative'.

'relative'
headroom_db float

Target peak in dBFS; default -1.

-1.0

Returns:

Type Description
list of float

One gain per signal.

AudioPlayer

AudioPlayer()

Plays a sound file or an array, on Windows and macOS.

Attributes:

Name Type Description
system str

"Windows" or "Darwin".

sound_type str

'file', 'array' or None.

duration property

duration

The length of the array last played, in seconds; 0 for a file or nothing.

playing property

playing

Whether a sound is playing now.

position property

position

Where the array's playback is, in seconds: it runs with the clock while playing, and holds when paused.

pause

pause()

Stop an array's playback and keep its position; resume plays on from it. A file stops.

play

play(sound, sampling_rate=None, normalize=True, gain_db=0.0, start=0.0)

Play a file path or an array; the other arguments are those of play_array, a file plays whole at its level.

play_array

play_array(array, sampling_rate, normalize=True, gain_db=0.0, start=0.0)

Play an array.

Parameters:

Name Type Description Default
array ndarray

(samples,) or (samples, channels). A mono signal plays on both channels.

required
sampling_rate int

Default: Defaults.SAMPLING_RATE.

required
normalize bool

Scale to a peak of 0.99; disable to play an auralisation at its rendered level.

True
gain_db float

Applied after the normalisation.

0.0
start float

In seconds from the beginning.

0.0

play_file

play_file(file_path)

Play a .wav file.

resume

resume()

Play on from where pause stopped.

stop

stop()

Stop the playback and return to the beginning.

Settings

Defaults

DEFAULT_SPEED_OF_SOUND property

DEFAULT_SPEED_OF_SOUND

The speed of sound of AirConditions() (20 deg C, 50 %, 101325 Pa), in m/s.

Worker

The Python process the Grasshopper components compute in; a script can use it too.

client

client()

This session's worker, started on first use; None if no worker interpreter is configured.