@@ -133,20 +133,24 @@ def _adjust_coeff(coeffs: Union[float, torch.Tensor], name: str) -> torch.Tensor
133
133
"""
134
134
num_walls = 6
135
135
if isinstance (coeffs , float ):
136
+ if coeffs < 0 :
137
+ raise ValueError (f"`{ name } ` must be non-negative. Found: { coeffs } " )
136
138
return torch .full ((1 , num_walls ), coeffs )
137
139
if isinstance (coeffs , Tensor ):
140
+ if torch .any (coeffs < 0 ):
141
+ raise ValueError (f"`{ name } ` must be non-negative. Found: { coeffs } " )
138
142
if coeffs .ndim == 1 :
139
143
if coeffs .numel () != num_walls :
140
144
raise ValueError (
141
- f"The shape of `{ name } ` must be ({ num_walls } ,) when it is a 1D Tensor."
145
+ f"The shape of `{ name } ` must be ({ num_walls } ,) when it is a 1D Tensor. "
142
146
f"Found the shape { coeffs .shape } ."
143
147
)
144
148
return coeffs .unsqueeze (0 )
145
149
if coeffs .ndim == 2 :
146
- if coeffs .shape != ( 7 , num_walls ) :
150
+ if coeffs .shape [ 1 ] != num_walls :
147
151
raise ValueError (
148
- f"The shape of `{ name } ` must be (7 , { num_walls } ) when it is a 2D Tensor. "
149
- f"Found the shape { coeffs .shape } ."
152
+ f"The shape of `{ name } ` must be (NUM_BANDS , { num_walls } ) when it "
153
+ f"is a 2D Tensor. Found: { coeffs .shape } ."
150
154
)
151
155
return coeffs
152
156
raise TypeError (f"`{ name } ` must be float or Tensor." )
@@ -169,7 +173,7 @@ def _validate_inputs(
169
173
if not (source .ndim == 1 and source .numel () == 3 ):
170
174
raise ValueError (f"`source` must be 1D Tensor with 3 elements. Found { source .shape } ." )
171
175
if not (mic_array .ndim == 2 and mic_array .shape [1 ] == 3 ):
172
- raise ValueError (f"mic_array must be a 2D Tensor with shape (num_channels, 3). Found { mic_array .shape } ." )
176
+ raise ValueError (f"` mic_array` must be a 2D Tensor with shape (num_channels, 3). Found { mic_array .shape } ." )
173
177
174
178
175
179
def simulate_rir_ism (
@@ -270,3 +274,106 @@ def simulate_rir_ism(
270
274
rir = rir [..., :output_length ]
271
275
272
276
return rir
277
+
278
+
279
+ def ray_tracing (
280
+ room : torch .Tensor ,
281
+ source : torch .Tensor ,
282
+ mic_array : torch .Tensor ,
283
+ num_rays : int ,
284
+ absorption : Union [float , torch .Tensor ] = 0.0 ,
285
+ scattering : Union [float , torch .Tensor ] = 0.0 ,
286
+ mic_radius : float = 0.5 ,
287
+ sound_speed : float = 343.0 ,
288
+ energy_thres : float = 1e-7 ,
289
+ time_thres : float = 10.0 ,
290
+ hist_bin_size : float = 0.004 ,
291
+ ) -> torch .Tensor :
292
+ r"""Compute energy histogram via ray tracing.
293
+
294
+ The implementation is based on *pyroomacoustics* :cite:`scheibler2018pyroomacoustics`.
295
+
296
+ ``num_rays`` rays are casted uniformly in all directions from the source;
297
+ when a ray intersects a wall, it is reflected and part of its energy is absorbed.
298
+ It is also scattered (sent directly to the microphone(s)) according to the ``scattering``
299
+ coefficient.
300
+ When a ray is close to the microphone, its current energy is recorded in the output
301
+ histogram for that given time slot.
302
+
303
+ .. devices:: CPU
304
+
305
+ .. properties:: TorchScript
306
+
307
+ Args:
308
+ room (torch.Tensor): Room coordinates. The shape of `room` must be `(3,)` which represents
309
+ three dimensions of the room.
310
+ source (torch.Tensor): Sound source coordinates. Tensor with dimensions `(3,)`.
311
+ mic_array (torch.Tensor): Microphone coordinates. Tensor with dimensions `(channel, 3)`.
312
+ absorption (float or torch.Tensor, optional): The absorption coefficients of wall materials.
313
+ (Default: ``0.0``).
314
+ If the type is ``float``, the absorption coefficient is identical to all walls and
315
+ all frequencies.
316
+ If ``absorption`` is a 1D Tensor, the shape must be `(6,)`, representing absorption
317
+ coefficients of ``"west"``, ``"east"``, ``"south"``, ``"north"``, ``"floor"``, and
318
+ ``"ceiling"``, respectively.
319
+ If ``absorption`` is a 2D Tensor, the shape must be `(num_bands, 6)`.
320
+ ``num_bands`` is the number of frequency bands (usually 7).
321
+ scattering(float or torch.Tensor, optional): The scattering coefficients of wall materials. (Default: ``0.0``)
322
+ The shape and type of this parameter is the same as for ``absorption``.
323
+ mic_radius(float, optional): The radius of the microphone in meters. (Default: 0.5)
324
+ sound_speed (float, optional): The speed of sound in meters per second. (Default: ``343.0``)
325
+ energy_thres (float, optional): The energy level below which we stop tracing a ray. (Default: ``1e-7``)
326
+ The initial energy of each ray is ``2 / num_rays``.
327
+ time_thres (float, optional): The maximal duration for which rays are traced. (Unit: seconds) (Default: 10.0)
328
+ hist_bin_size (float, optional): The size of each bin in the output histogram. (Unit: seconds) (Default: 0.004)
329
+
330
+ Returns:
331
+ (torch.Tensor): The 3D histogram(s) where the energy of the traced ray is recorded.
332
+ Each bin corresponds to a given time slot.
333
+ The shape is `(channel, num_bands, num_bins)`, where
334
+ ``num_bins = ceil(time_thres / hist_bin_size)``.
335
+ If both ``absorption`` and ``scattering`` are floats, then ``num_bands == 1``.
336
+ """
337
+ if time_thres < hist_bin_size :
338
+ raise ValueError (
339
+ "`time_thres` must be greater than `hist_bin_size`. "
340
+ f"Found: hist_bin_size={ hist_bin_size } , time_thres={ time_thres } ."
341
+ )
342
+
343
+ if room .dtype != source .dtype or source .dtype != mic_array .dtype :
344
+ raise ValueError (
345
+ "dtype of `room`, `source` and `mic_array` must match. "
346
+ f"Found: `room` ({ room .dtype } ), `source` ({ source .dtype } ) and "
347
+ f"`mic_array` ({ mic_array .dtype } )"
348
+ )
349
+
350
+ _validate_inputs (room , source , mic_array )
351
+ absorption = _adjust_coeff (absorption , "absorption" ).to (room .dtype )
352
+ scattering = _adjust_coeff (scattering , "scattering" ).to (room .dtype )
353
+
354
+ # Bring absorption and scattering to the same shape
355
+ if absorption .shape [0 ] == 1 and scattering .shape [0 ] > 1 :
356
+ absorption = absorption .expand (scattering .shape )
357
+ if scattering .shape [0 ] == 1 and absorption .shape [0 ] > 1 :
358
+ scattering = scattering .expand (absorption .shape )
359
+ if absorption .shape != scattering .shape :
360
+ raise ValueError (
361
+ "`absorption` and `scattering` must be broadcastable to the same number of bands and walls. "
362
+ f"Inferred shapes absorption={ absorption .shape } and scattering={ scattering .shape } "
363
+ )
364
+
365
+ histograms = torch .ops .torchaudio .ray_tracing (
366
+ room ,
367
+ source ,
368
+ mic_array ,
369
+ num_rays ,
370
+ absorption ,
371
+ scattering ,
372
+ mic_radius ,
373
+ sound_speed ,
374
+ energy_thres ,
375
+ time_thres ,
376
+ hist_bin_size ,
377
+ )
378
+
379
+ return histograms
0 commit comments