audiblelight.worldstate.WorldState#

class audiblelight.worldstate.WorldState(mesh, empty_space_around_mic=0.1, empty_space_around_emitter=0.2, empty_space_around_surface=0.2, empty_space_around_capsule=0.05, add_to_context=True, ensure_minimum_weighted_average_ray_length=False, minimum_weighted_average_ray_length=3.0, repair_threshold=None, rlr_kwargs=None)#

Bases: object

Represents a 3D space defined by a mesh, microphone position(s), and emitter position(s)

This class is capable of handling spatial operations and simulating audio propagation using the ray-tracing library.

Parameters:
  • mesh (str | Path)

  • empty_space_around_mic (int | float | complex | integer | floating | None)

  • empty_space_around_emitter (int | float | complex | integer | floating | None)

  • empty_space_around_surface (int | float | complex | integer | floating | None)

  • empty_space_around_capsule (int | float | complex | integer | floating | None)

  • add_to_context (bool | None)

  • ensure_minimum_weighted_average_ray_length (bool | None)

  • minimum_weighted_average_ray_length (int | float | complex | integer | floating | None)

  • repair_threshold (int | float | complex | integer | floating | None)

  • rlr_kwargs (dict | None)

mesh#

The path to the mesh on the disk.

Type:

str, Path

microphones#

Position of the microphone in the mesh.

Type:

np.array

ctx#

The context for audio propagation simulation.

Type:

rlr_audio_propagation.Context

emitters#

relative positions of sound emitter

Type:

np.array

__init__(mesh, empty_space_around_mic=0.1, empty_space_around_emitter=0.2, empty_space_around_surface=0.2, empty_space_around_capsule=0.05, add_to_context=True, ensure_minimum_weighted_average_ray_length=False, minimum_weighted_average_ray_length=3.0, repair_threshold=None, rlr_kwargs=None)#

Initializes the WorldState with a mesh and sets up the audio context.

Parameters:
  • mesh (str|Path) – The name of the mesh file. Units will be coerced to meters when loading

  • empty_space_around_mic (float) – minimum meters new emitters/mics will be placed from center of other mics

  • empty_space_around_emitter (float) – minimum meters new emitters/mics will be placed from other emitters

  • empty_space_around_surface (float) – minimum meters new emitters/mics will be placed from mesh emitters

  • empty_space_around_capsule (float) – minimum meters new emitters/mics will be placed from mic capsules

  • add_to_context (bool) – if False, the ray-tracing context will ONLY be updated when running WorldState.simulate. This is ideal in large-scale data generation pipelines. If True, the state will be updated every time a new Microphone or Emitter is added. This is ideal for interactive use.

  • ensure_minimum_weighted_average_ray_length (bool) – if True, random points can only be sampled from within the mesh when they have a weighted average ray length of at least minimum_weighted_average_ray_length

  • minimum_weighted_average_ray_length (float) – value to consider when locating points in the mesh; only evaluated when ensure_minimum_weighted_average_ray_length is True

  • repair_threshold (float, optional) – when the proportion of broken faces on the mesh is below this value, repair the mesh and fill holes. If None, will never repair the mesh.

  • rlr_kwargs (dict, optional) – additional keyword arguments to pass to the RLR audio propagation library. For instance, sample rate can be set by passing rlr_kwargs=dict(sample_rate=…)

Methods

__init__(mesh[, empty_space_around_mic, ...])

Initializes the WorldState with a mesh and sets up the audio context.

add_emitter([position, alias, mic, ...])

Add an emitter to the state.

add_emitters([positions, aliases, mics, ...])

Add emitters to the mesh.

add_microphone([microphone_type, position, ...])

Add a microphone to the space.

add_microphone_and_emitter([position, ...])

Add both a microphone and emitter with specified relationship.

add_microphones([microphone_types, ...])

Add multiple microphones to the mesh.

calculate_weighted_average_ray_length(point)

Estimate how spatially "open" a point is by computing the weighted average length of rays cast from that point.

clear_emitter(alias)

Given an alias for an emitter, clear that emitter and update the state.

clear_emitters()

Removes all current emitters.

clear_microphone(alias)

Given an alias for a microphone, clear that microphone if it exists and update the state.

clear_microphones()

Removes all current microphones.

create_plot()

Creates a matplotlib.Figure object corresponding to top-down and side-views of the scene

create_scene([mic_radius, emitter_radius])

Creates a trimesh.Scene with the Space's mesh, microphone position, and emitters all added

define_trajectory(duration[, ...])

Defines a trajectory for a moving sound event with specified spatial bounds and event duration.

from_dict(input_dict)

Instantiate a WorldState from a dictionary.

get_emitter(alias[, emitter_idx])

Given a valid alias and index, get a single Emitter object, as in self.emitters[alias][emitter_idx]

get_emitters(alias)

Given a valid alias, get a list of associated Emitter objects, as in self.emitters[alias]

get_irs()

Get the IRs from the ray-tracing context

get_microphone(alias)

Given a valid alias, get an associated Microphone object, as in self.microphones[alias].

get_random_point_inside_mesh([batch_size])

Generates a random valid point inside the mesh.

get_random_position()

Get a random position to place an object inside the mesh

get_valid_position_with_max_distance(ref, r)

Generate a sphere with origin ref and radius r and sample a valid position from within its volume.

path_exists_between_points(point_a, point_b)

Returns True if a direct point exists between point_a and point_b in the mesh, False otherwise.

save_irs_to_wav(outdir)

Writes IRs to WAV audio files.

simulate()

Simulates audio propagation in the state with the current listener and sound emitter positions.

to_dict()

Returns metadata for this object as a dictionary

Attributes

irs

(N_capsules, N_emitters, N_samples), mic001: (...)}

num_emitters

Returns the number of emitters in the state.

__eq__(other)#

Compare two WorldState objects for equality.

Returns:

True if two WorldState objects are equal, False otherwise

Return type:

bool

Parameters:

other (Any)

__getitem__(alias)#

An alternative for self.get_emitters(alias) or `self.emitters[alias]

Parameters:

alias (str)

Return type:

list[Emitter]

__len__()#

Returns the number of objects in the mesh (i.e., number of microphones + emitters)

Return type:

int

__repr__()#

Returns a JSON-formatted string representation of the WorldState

Return type:

str

__str__()#

Returns a string representation of the WorldState

Return type:

str

add_emitter(position=None, alias=None, mic=None, keep_existing=False, ensure_direct_path=False)#

Add an emitter to the state.

If position is provided, it must be in absolute (cartesian) terms. If mic is a key inside microphones, position is assumed to be relative to that microphone.

Parameters:
  • position (list | ndarray | None) – Location to add the emitter, defaults to a random, valid location.

  • alias (str | None) – String reference to access the emitter inside the self.emitters dictionary.

  • mic (str | None) – String reference to a microphone inside self.microphones; when provided, position is interpreted as RELATIVE to the center of this microphone

  • keep_existing (optional) – Whether to keep existing emitters from the mesh or remove, defaults to keep

  • ensure_direct_path (bool | list | str | None) – Whether to ensure a direct line exists between the emitter and given microphone(s). If True, will ensure a direct line exists between the emitter and ALL microphone objects. If a list of strings, these should correspond to microphone aliases inside microphones; a direct line will be ensured with all of these microphones. If False, no direct line is required for a emitter.

Return type:

None

Examples

Create a state with a given mesh and add a microphone >>> spa = WorldState(mesh=…) >>> spa.add_microphone(alias=”tester”)

Add a single emitter with a random position >>> spa.add_emitter() >>> spa.get_emitter(“src000”) # access with default alias

Add emitter with given position and alias >>> spa.add_emitter(position=[0.5, 0.5, 0.5], alias=”custom”) >>> spa.get_emitter(“custom”) # access using given alias

Add emitter relative to microphone >>> spa.add_emitter(position=[0.1, 0.1, 0.1], alias=”custom”, mic=”tester”) >>> spa.get_emitter(“custom”)

Add emitter with a random position that is in a direct line with the microphone we placed above >>> spa.add_emitter(ensure_direct_path=”tester”)

add_emitters(positions=None, aliases=None, mics=None, n_emitters=None, keep_existing=False, ensure_direct_path=False, raise_on_error=True)#

Add emitters to the mesh.

This function essentially takes in lists of the arguments expected by add_emitters. The raise_on_error command will skip over microphones that cannot be placed in the mesh and raise a warning in the console.

Additionally, n_emitters can be provided instead of positions to choose a number of emitters to add randomly.

Parameters:
  • positions (list | ndarray | None) – Locations to add the emitters, defaults to a single random location.

  • aliases (list[str] | None) – String references to assign the emitters inside the emitters dictionary.

  • mics (list[str] | str | None) – String references to microphones inside the microphones dictionary.

  • keep_existing (optional) – whether to keep existing emitters from the mesh or remove, defaults to keep.

  • raise_on_error (optional) – if True, raises an error when unable to place emitter, otherwise skips to next.

  • n_emitters (int | None) – Number of emitters to add with random positions

  • ensure_direct_path (bool | list | str | None) – Whether to ensure a direct line exists between the emitter and given microphone(s). If True, will ensure a direct line exists between the emitter and ALL microphone objects. If a list of strings, these should correspond to microphone aliases inside microphones; a direct line will be ensured with all of these microphones. If False, no direct line is required for a emitter.

Return type:

None

add_microphone(microphone_type=None, position=None, alias=None, keep_existing=True)#

Add a microphone to the space.

Parameters:
  • microphone_type (str | Type[MicArray] | None) – Type of microphone to add, defaults to a mono capsule.

  • position (list | ndarray | None) – Location to add the microphone in absolute cartesian units, defaults to a random, valid location.

  • alias (str | None) – String reference to access the microphone inside the self.microphones dictionary.

  • keep_existing (optional) – whether to keep existing microphones from the mesh or remove, defaults to keep

Return type:

None

Examples

Create a state from a given mesh >>> spa = WorldState(mesh=…)

Add a AmbeoVR microphone with a random position and default alias >>> spa.add_microphone(“ambeovr”) >>> spa.microphones[“mic000”] # access with default alias

Alternative, using MicArray objects >>> from audiblelight.micarrays import AmbeoVR >>> spa.add_microphone(AmbeoVR)

Add AmbeoVR with given position and alias >>> spa.add_microphone(microphone_type=”ambeovr”, position=[0.5, 0.5, 0.5], alias=”ambeo”) >>> spa.microphones[“ambeo”] # access using given alias

add_microphone_and_emitter(position=None, polar=True, microphone_type=None, mic_alias=None, emitter_alias=None, keep_existing_mics=True, keep_existing_emitters=True, ensure_direct_path=True, max_place_attempts=1000)#

Add both a microphone and emitter with specified relationship.

The microphone will be placed in a random, valid position. The emitter will then be placed relative to the microphone, either in Cartesian or spherical coordinates.

Parameters:
  • position (np.ndarray) – Array of form [X, Y, Z]

  • polar (bool | None) – whether the coordinates are provided in spherical form. If True: - Azimuth (X) must be between 0 and 360 - Elevation (Y) must be between -90 and 90 - Radius (Z) must be a positive value, measured in the same units given by the mesh.

  • microphone_type (str | Type[MicArray] | None) – Type of microphone to add, defaults to mono capsule

  • mic_alias (str | None) – String reference for the microphone, auto-generated if None

  • emitter_alias (str | None) – String reference for the emitter, auto-generated if None

  • keep_existing_mics (bool | None) – Whether to keep existing microphones, defaults to True

  • keep_existing_emitters (bool | None) – Whether to keep existing emitters, defaults to True

  • ensure_direct_path (bool | None) – Whether to ensure line-of-sight between mic and emitter

  • max_place_attempts (int | None) – The number of times to try placing the microphone and emitter

Raises:

ValueError – If unable to place microphone and emitter within the mesh

Return type:

None

Examples

# Create a state with a given mesh >>> spa = WorldState(mesh=…)

# Place emitter 2 meters in front of microphone >>> spa.add_microphone_and_emitter(np.array([0, 0, 2.0]))

# Place emitter 1.5 meters to the left and slightly above >>> spa.add_microphone_and_emitter(np.array([90, 30, 1.5]), mic_alias=”main_mic”, emitter_alias=”left_source”)

# Place emitter behind and below >>> spa.add_microphone_and_emitter(np.array([180, -45, 1.0]))

add_microphones(microphone_types=None, positions=None, aliases=None, keep_existing=True, raise_on_error=True)#

Add multiple microphones to the mesh.

This function essentially takes in lists of the arguments expected by add_microphone. The raise_on_error command will skip over microphones that cannot be placed in the mesh and raise a warning in the console.

Parameters:
  • microphone_types (list[str | Type[MicArray]] | None) – Types of microphones to add, defaults to a single mono capsule.

  • positions (list[list | ndarray] | None) – Locations to add the microphones in absolute cartesian units, defaults to a single location.

  • aliases (list[str] | None) – String references to access the microphones inside the self.microphones dictionary.

  • keep_existing (optional) – whether to keep existing microphones from the mesh or remove, defaults to keep

  • raise_on_error (optional) – if True, will raise an error when unable to place a mic, otherwise skips to next

Return type:

None

Examples

Create a state with a given mesh >>> spa = WorldState(mesh=…)

Add some AmbeoVRs with random positions >>> spa.add_microphones(microphone_types=[“ambeovr”, “ambeovr”, “ambeovr”]) >>> spa.microphones[“mic002”] # access with default alias

Add AmbeoVR and Eigenmike32 with given positions and aliases >>> spa.add_microphones( >>> microphone_types=[“ambeovr”, “eigenmike32”], >>> positions=[[0.5, 0.5, 0.5], [0.1, 0.1, 0.1]], >>> alias=[“ambeo”, “eigen”], >>> keep_existing=False, # removes microphones already added to the space >>> raise_on_error=True, # raises an error if any microphone cannot be placed >>> ) >>> spa.microphones[“eigen”] # access using given alias

calculate_weighted_average_ray_length(point, num_rays=100)#

Estimate how spatially “open” a point is by computing the weighted average length of rays cast from that point.

Rays are emitted uniformly from the given point across 3D space. Each ray is traced until it intersects with the mesh surface. The distances of these intersections are squared and used as weights to calculate a weighted average, emphasising longer, unobstructed paths. This can be used as a heuristic for how suitable a point is within a mesh (e.g., avoiding corners)

If any rays fail to intersect the mesh (due to holes or open surfaces), those rays are ignored, and a warning is logged.

Parameters:
  • point (np.ndarray) – a 3D coordinate (shape: (3,)) representing the origin of the rays.

  • num_rays (int) – number of random rays to cast from the point (default is 100).

Returns:

The weighted average distance of ray intersections: higher values indicate more open surroundings

Return type:

float

clear_emitter(alias)#

Given an alias for an emitter, clear that emitter and update the state.

Parameters:

alias (str)

Return type:

None

clear_emitters()#

Removes all current emitters.

Return type:

None

clear_microphone(alias)#

Given an alias for a microphone, clear that microphone if it exists and update the state.

Parameters:

alias (str)

Return type:

None

clear_microphones()#

Removes all current microphones.

Return type:

None

create_plot()#

Creates a matplotlib.Figure object corresponding to top-down and side-views of the scene

Returns:

The rendered figure that can be shown with e.g. plt.show()

Return type:

plt.Figure

create_scene(mic_radius=0.2, emitter_radius=0.1)#

Creates a trimesh.Scene with the Space’s mesh, microphone position, and emitters all added

Returns:

The rendered scene, that can be shown in e.g. a notebook with the .show() command

Return type:

trimesh.Scene

Parameters:
  • mic_radius (int | float | complex | integer | floating | None)

  • emitter_radius (int | float | complex | integer | floating | None)

define_trajectory(duration, starting_position=None, velocity=0.75, resolution=1.5, shape='linear', max_place_attempts=1000)#

Defines a trajectory for a moving sound event with specified spatial bounds and event duration.

This method calculates a series of XYZ coordinates that outline the path of a sound event, based on the specified trajectory shape, the confines of the mesh, and the duration of the event. It generates a starting point and an end point that comply with these conditions, and then interpolates between these points according to the trajectory’s shape.

Optionally, a custom starting position can also be provided, and a valid ending position will be randomly sampled. To provide a custom ENDING position, one possibility is to provide this as the starting position, then invert the trajectory array returned by this function. To use both a custom start AND end position, call the trajectory functions in utils.py directly.

Parameters:
  • duration (Numeric) – the length of time it should take to traverse from starting to ending position

  • starting_position (np.ndarray) – the starting position for the trajectory. If not provided, a random valid position within the mesh will be selected.

  • velocity (Numeric) – the speed limit for the trajectory, in meters per second

  • resolution (Numeric) – the number of emitters created per second

  • shape (str) – the shape of the trajectory; currently, only “linear” and “circular” are supported.

  • max_place_attempts (Numeric) – the number of times to try and create the trajectory.

Raises:

ValueError – if a trajectory cannot be defined after max_place_attempts

Returns:

the sanitised trajectory, with shape (n_points, 3)

Return type:

np.ndarray

classmethod from_dict(input_dict)#

Instantiate a WorldState from a dictionary.

Parameters:

input_dict (dict[str, Any]) – Dictionary that will be used to instantiate the WorldState.

Returns:

WorldState instance.

get_emitter(alias, emitter_idx=0)#

Given a valid alias and index, get a single Emitter object, as in self.emitters[alias][emitter_idx]

Parameters:
  • alias (str)

  • emitter_idx (int | None)

Return type:

Emitter

get_emitters(alias)#

Given a valid alias, get a list of associated Emitter objects, as in self.emitters[alias]

Parameters:

alias (str)

Return type:

list[Emitter]

get_irs()#

Get the IRs from the ray-tracing context

By default, Context.get_audio expects all listeners to have the same channel layout type. If, however, e.g. some listeners have Mono and others have Ambisonics, Context.get_audio will fail due to numpy expecting all arrays to have equal dims.

Instead, we need to return a dictionary of numpy arrays. The output will have shape: {mic1: (N_capsules, N_emitters, N_samples), …}

Return type:

OrderedDict[str, ndarray]

get_microphone(alias)#

Given a valid alias, get an associated Microphone object, as in self.microphones[alias].

Parameters:

alias (str)

Return type:

Type[MicArray]

get_random_point_inside_mesh(batch_size=10)#

Generates a random valid point inside the mesh.

N positions will be generated in batches and a whole batch will be checked at once to take advantage of numpy vectorisation. In initial experiments, using a batch size of 10 resulted in a speed-up of 1.5x over a batch size of 1 (i.e., no batching). Improvements level off and decrease after, with a batch size of 100 resulting in 2x worse performance than no batching.

Parameters:

batch_size (int) – number of points to generate in a single batch, defaults to 10

Returns:

A valid point within the mesh in XYZ format

Return type:

np.array

get_random_position()#

Get a random position to place an object inside the mesh

If ensure_minimum_weighted_average_ray_length is enabled, this function attempts to find a position that meets the minimum openness criteria, measured by the weighted average ray length from the candidate point. It will try up to MAX_PLACE_ATTEMPTS times before returning the last attempted position.

Returns:

the random position to place an object inside the mesh

Return type:

np.ndarray

get_valid_position_with_max_distance(ref, r, n=1000)#

Generate a sphere with origin ref and radius r and sample a valid position from within its volume.

Parameters:
  • ref (np.ndarray) – the reference point, treated as the origin of the sphere

  • r (custom_types.Numeric) – the maximum distance for the sampled point from ref

  • n (custom_types.Numeric) – the number of points to create on the sphere. Only the first valid point will be returned

Raises:

ValueError – if a valid point from within n samples cannot be found

Return type:

ndarray

property irs: OrderedDict[str, ndarray]#

(N_capsules, N_emitters, N_samples), mic001: (…)}

Type:

Returns a dictionary of IRs in the shape {mic000

property num_emitters: int#

Returns the number of emitters in the state.

Note that this is not the same as calling len(self.emitters): the total number of emitters is equivalent to the length of ALL lists inside this dictionary

path_exists_between_points(point_a, point_b)#

Returns True if a direct point exists between point_a and point_b in the mesh, False otherwise.

Parameters:
  • point_a (ndarray)

  • point_b (ndarray)

Return type:

bool

save_irs_to_wav(outdir)#

Writes IRs to WAV audio files.

IRs will be dumped in the form mic{i1}_capsule{i2}_emitter_{i3}.wav. For instance, with two emitters and two mono microphones, we’d expect mic000_capsule000_emitter000.wav, mic001_capsule_000_emitter_001.wav, mic001_capsule_000_emitter_000.wav, and mic002_capsule000_emitter_002.wav.

Parameters:

outdir (str) – IRs will be saved here.

Return type:

None

simulate()#

Simulates audio propagation in the state with the current listener and sound emitter positions.

Return type:

None

to_dict()#

Returns metadata for this object as a dictionary

Return type:

dict