#!/usr/bin/env python
# coding: utf-8
# # Directivities demo
#
# The goal of this notebook is to demonstrate how microphone and source directivities can be taken into account in room impulse response (RIR) simulation with `pyroomacoustics`.
#
# At the time of writing (August 31, 2021), the following is supported:
# - Microphone directivities with the Image Source Method (ISM) for shoebox and arbitrary shape rooms.
# - Source directivies with ISM for only shoebox rooms.
#
# Below is a list of what this notebook covers.
#
# 1. [Available directivity patterns](#pattern)
# 2. [Microphone directivity](#mic)
# 3. [Rotating microphone](#rotate)
# 4. [Source directivity](#source)
# 5. [Microphone array directivities](#micarray)
# - [Same directivity for all microphones](#same)
# - [Different directivity for each microphone](#diff)
# - [Helper function for circular microphone arrays](#circ)
#
# Let's begin with the necessary imports.
# In[1]:
import numpy as np
import matplotlib.pyplot as plt
import pyroomacoustics as pra
from pyroomacoustics import dB
from pyroomacoustics.directivities import (
DirectivityPattern,
DirectionVector,
CardioidFamily,
)
from pyroomacoustics.doa import spher2cart
from scipy.io import wavfile
import IPython
fs, signal = wavfile.read("arctic_a0010.wav")
signal = signal / np.max(np.abs(signal)) # so that simulation is within [-1, 1] for normalize=False
#
# ## Available directivity patterns
#
# There are four (frequency-independent) directivities that can be used out-of-the box:
# - Figure eight
# - Hyper-cardioid
# - Cardioid
# - Sub-cardioid
#
# Below is a comparison of their 2D polar patterns.
# In[2]:
# angles to plot over
angles = np.linspace(start=0, stop=360, num=361, endpoint=True)
angles = np.radians(angles)
lower_gain = -20
# plot each pattern
fig = plt.figure()
ax = plt.subplot(111, projection="polar")
for pattern in DirectivityPattern:
dir_obj = CardioidFamily(
orientation=DirectionVector(azimuth=0, colatitude=90, degrees=True),
pattern_enum=pattern
)
resp = dir_obj.get_response(azimuth=angles, magnitude=True, degrees=False)
resp_db = dB(np.array(resp))
ax.plot(angles, resp_db, label=pattern.name)
plt.legend(bbox_to_anchor=(1, 1))
plt.ylim([lower_gain, 0])
ax.yaxis.set_ticks(np.arange(start=lower_gain, stop=5, step=5))
plt.tight_layout()
# Support for 3D is also available.
# In[3]:
pattern = DirectivityPattern.HYPERCARDIOID
orientation = DirectionVector(azimuth=45, colatitude=45, degrees=True)
# create cardioid object
dir_obj = CardioidFamily(orientation=orientation, pattern_enum=pattern)
# plot
azimuth = np.linspace(start=0, stop=360, num=361, endpoint=True)
colatitude = np.linspace(start=0, stop=180, num=180, endpoint=True)
dir_obj.plot_response(azimuth=azimuth, colatitude=colatitude, degrees=True);
#
# ## Microphone directivity
#
# Adding a directivity to a microphone is as simple as passing a `Directivity` object to the a room's `add_microphone` method.
# In[4]:
mic_pattern = DirectivityPattern.HYPERCARDIOID
# create room with single source and microphone
room = pra.ShoeBox(
p=[5, 3, 3],
materials=pra.Material(0.4),
fs=16000,
max_order=40,
)
room.add_source(position=[1, 1, 1.8])
mic_dir = CardioidFamily(
orientation=DirectionVector(azimuth=180, colatitude=60, degrees=True),
pattern_enum=mic_pattern,
)
room.add_microphone(loc=[3.5, 1.8, 1.0], directivity=mic_dir);
# We can then plot the room to see the set directivity.
# In[5]:
fig, ax = room.plot()
ax.set_xlim([-1, 6])
ax.set_ylim([-1, 4])
ax.set_zlim([-1, 4])
ax.set_title("")
fig.set_size_inches(10, 5);
# Simulating the RIR will take into account the specified directivity.
# In[6]:
room.plot_rir()
#
# ## Rotating microphone
#
# Let's do a simple listening test to see how setting the directivity affects the simulation of a source, namely we will rotate the microphone's directivity pattern.
#
# Here's the original file.
# In[7]:
print("Original WAV:")
IPython.display.Audio(signal, rate=fs, normalize=False)
# In[8]:
# rotate microphone directivity and simulate
n = 4
pattern = DirectivityPattern.CARDIOID
res = []
for i in range(n):
angle = 360 * i / n
print("ANGLE : {}".format(angle))
# create room
room_dim = [6.5, 3, 3]
room = pra.ShoeBox(
p=room_dim,
materials=pra.Material(0.6),
fs=16000,
max_order=20,
)
# add source signal
source_loc = [1, 1.5, 1.5]
room.add_source(source_loc, signal=signal)
# add microphone with directivity
mic_loc = [3.5, 1.5, 1.5]
directivity = CardioidFamily(
orientation=DirectionVector(azimuth=angle, colatitude=90, degrees=True),
pattern_enum=pattern
)
room.add_microphone(mic_loc, directivity=directivity)
# simulate
room.simulate()
est_rt_60 = pra.experimental.rt60.measure_rt60(room.rir[0][0], fs=16000)
print("Estimated RT60 : {}".format(est_rt_60))
# plot bird's eye view
room_2d = pra.ShoeBox(p=room_dim[:2])
room_2d.add_source(source_loc[:2])
directivity_2d = CardioidFamily(
orientation=DirectionVector(azimuth=angle, degrees=True),
pattern_enum=pattern
)
room_2d.add_microphone(mic_loc[:2], directivity=directivity_2d)
_, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 2.5))
room_2d.plot(ax=ax[0])
n_samples = len(room.rir[0][0])
t_vals = np.arange(n_samples) / room.fs
ax[1].plot(t_vals, room.rir[0][0])
ax[1].set_xlabel("Time [s]")
ax[1].set_ylim([-0.1, 0.6])
plt.show()
# output signal
IPython.display.display(IPython.display.Audio(room.mic_array.signals[0,:], rate=fs, normalize=False))
res.append(room.rir[0][0])
# We can simulation an omnidirectional microphone for comparison.
# In[9]:
# create room
room_dim = [6.5, 3, 3]
room = pra.ShoeBox(
p=room_dim,
materials=pra.Material(0.6),
fs=16000,
max_order=20,
)
# add source signal
source_loc = [1, 1.5, 1.5]
room.add_source(source_loc, signal=signal)
# add microphone with directivity
mic_loc = [3.5, 1.5, 1.5]
room.add_microphone(mic_loc)
# simulate
room.simulate()
est_rt_60 = pra.experimental.rt60.measure_rt60(room.rir[0][0], fs=16000)
print("Estimated RT60 : {}".format(est_rt_60))
# plot bird's eye view
room_2d = pra.ShoeBox(p=room_dim[:2])
room_2d.add_source(source_loc[:2])
room_2d.add_microphone(mic_loc[:2])
_, ax = plt.subplots(nrows=1, ncols=1)
n_samples = len(room.rir[0][0])
t_vals = np.arange(n_samples) / room.fs
ax.plot(t_vals, room.rir[0][0], label="omni", alpha=0.7)
ax.plot(t_vals, res[2], label="directivity, angle=180", alpha=0.7)
ax.set_xlabel("Time [s]")
ax.set_ylim([-0.1, 0.7])
ax.legend()
plt.show()
# output signal
IPython.display.display(IPython.display.Audio(room.mic_array.signals[0,:], rate=fs, normalize=False))
res.append(room.rir[0][0])
# Notice how the RIR simulation with the directivity is more attenuated than the omnidirectional one.
#
# ## Source directivity
#
# Adding a directivity to a source is as simple as passing a `Directivity` object to the room's `add_source` method.
# In[10]:
source_pattern = DirectivityPattern.HYPERCARDIOID
# create room with single source and microphone
room = pra.ShoeBox(
p=[5, 3, 3],
materials=pra.Material(0.4),
fs=16000,
max_order=40,
)
source_dir = CardioidFamily(
orientation=DirectionVector(azimuth=0, colatitude=60, degrees=True),
pattern_enum=source_pattern,
)
room.add_source(position=[1, 1, 1.8], directivity=source_dir)
room.add_microphone(loc=[3.5, 1.8, 1.0]);
# We can then plot the room to see the set directivity.
# In[11]:
fig, ax = room.plot()
ax.set_xlim([-1, 6])
ax.set_ylim([-1, 4])
ax.set_zlim([-1, 4])
ax.set_title("")
fig.set_size_inches(10, 5);
#
# ## Microphone array directivities
#
# For specifying the directivities of multiple microphones at once, there are a few approaches.
#
# ### 1) Same directivity for all microphones
#
# Just requires creating one `Directivity` object. Note that we use the `add_microphone_array` method of the `Room` object!
# In[12]:
pattern = DirectivityPattern.HYPERCARDIOID
orientation = DirectionVector(azimuth=0, colatitude=0, degrees=True)
# create room
room = pra.ShoeBox(
p=[7, 7, 3],
materials=pra.Material(0.4),
fs=16000,
max_order=40,
)
# add source
room.add_source([1, 1, 1.7])
# add linear microphone array
M = 3
R = pra.linear_2D_array(center=[5, 5], M=M, phi=0, d=0.7)
R = np.concatenate((R, np.ones((1, M))))
directivity = CardioidFamily(orientation=orientation, pattern_enum=pattern)
room.add_microphone_array(R, directivity=directivity)
# plot room
fig, ax = room.plot()
ax.set_title("")
ax.set_xlim([-1, 8])
ax.set_ylim([-1, 8])
ax.set_zlim([-1, 4])
# plot
room.plot_rir()
#
# ### 2) Different directivity for each microphone
#
# For this, a separate `Directivity` object needs to be created for each microphone.
# In[13]:
dir_1 = CardioidFamily(
orientation=DirectionVector(azimuth=180, colatitude=30, degrees=True),
pattern_enum=DirectivityPattern.HYPERCARDIOID,
)
dir_2 = CardioidFamily(
orientation=DirectionVector(azimuth=0, colatitude=30, degrees=True),
pattern_enum=DirectivityPattern.HYPERCARDIOID,
)
# create room
room = pra.ShoeBox(
p=[7, 7, 3],
materials=pra.Material(0.4),
fs=16000,
max_order=40,
)
# add source
room.add_source([1, 1, 1.7])
# add linear microphone array
M = 2
R = pra.linear_2D_array(center=[5, 5], M=M, phi=0, d=0.7)
R = np.concatenate((R, np.ones((1, M))))
room.add_microphone_array(R, directivity=[dir_1, dir_2])
# plot room
fig, ax = room.plot()
ax.set_xlim([-1, 8])
ax.set_ylim([-1, 8])
ax.set_zlim([-1, 4])
# plot
room.plot_rir()
#
# ### 3) Helper function for circular microphone arrays
#
# For such arrays, the function `pyroomacoustics.beamforming.circular_microphone_array_xyplane` can be to create a `MicrophoneArray` with a circular array configuration and the specified directivity facing outwards.
# In[14]:
mic_rotation = 0
room_dim = [7, 7, 5]
source_loc = [5, 2.5, 4]
center = [3, 3, 2]
colatitude = 90
# make a room
room = pra.ShoeBox(p=room_dim)
# add source
room.add_source(source_loc)
# add circular microphone array
pattern = DirectivityPattern.CARDIOID
orientation = DirectionVector(azimuth=mic_rotation, colatitude=colatitude, degrees=True)
directivity = CardioidFamily(orientation=orientation, pattern_enum=pattern)
mic_array = pra.beamforming.circular_microphone_array_xyplane(
center=center,
M=7,
phi0=mic_rotation,
radius=50e-2,
fs=room.fs,
directivity=directivity,
)
room.add_microphone_array(mic_array)
# plot everything
fig, ax = room.plot()
ax.set_xlim([-1, 8])
ax.set_ylim([-1, 8])
ax.set_zlim([-1, 6]);
# It also works in 2D.
# In[15]:
mic_rotation = 0
room_dim = [7, 7]
source_loc = [5, 2.5]
center = [3, 3]
# make a room
room = pra.ShoeBox(p=room_dim)
# add source
room.add_source(source_loc)
# add circular microphone array
pattern = DirectivityPattern.HYPERCARDIOID
orientation = DirectionVector(azimuth=mic_rotation, degrees=True)
directivity = CardioidFamily(orientation=orientation, pattern_enum=pattern)
mic_array = pra.beamforming.circular_microphone_array_xyplane(
center=center,
M=7,
phi0=mic_rotation,
radius=100e-2,
fs=room.fs,
directivity=directivity,
)
room.add_microphone_array(mic_array)
# plot everything
fig, ax = room.plot()
ax.set_xlim([-1, 8])
ax.set_ylim([-1, 8]);
# In[ ]: