Skip to content

Commit 234bbdd

Browse files
authored
Merge pull request #5 from spatialaudio/sfa_circular_harmonics
Radial weight vectors in CHT
2 parents 43329a7 + 1acd362 commit 234bbdd

File tree

3 files changed

+46
-61
lines changed

3 files changed

+46
-61
lines changed

examples/modal_beamforming_open_circular_array.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,51 @@
66
import numpy as np
77
import matplotlib.pyplot as plt
88
import micarray
9+
from micarray.util import db
910

10-
N = 90 # order of modal beamformer/microphone array
11+
Nsf = 50 # order of the incident sound field
12+
N = 30 # order of modal beamformer/microphone array
1113
pw_angle = 1.23 * np.pi # incidence angle of plane wave
12-
pol_pwd = np.linspace(0, 2*np.pi, 91, endpoint=False) # angles for plane wave decomposition
14+
pol_pwd = np.linspace(0, 2*np.pi, 180, endpoint=False) # angles for plane wave decomposition
1315
k = np.linspace(0.1, 20, 100) # wavenumber vector
1416
r = 1 # radius of array
1517

1618
# get uniform grid (microphone positions) of order N
1719
pol, weights = micarray.modal.angular.grid_equal_polar_angle(N)
18-
# get circular harmonics matrix for sensors
19-
Psi_p = micarray.modal.angular.cht_matrix(N, pol, weights)
20-
# get circular harmonics matrix for a source ensemble of azimuthal plane wave
21-
Psi_q = micarray.modal.angular.cht_matrix(N, pol_pwd)
22-
# get radial filters
20+
21+
# pressure on the surface of a rigid cylinder for an incident plane wave
22+
Bn = micarray.modal.radial.circular_pw(Nsf, k, r, setup='open')
23+
D = micarray.modal.radial.circ_diagonal_mode_mat(Bn)
24+
Psi_p = micarray.modal.angular.cht_matrix(Nsf, pol)
25+
Psi_pw = micarray.modal.angular.cht_matrix(Nsf, pw_angle)
26+
p = np.matmul(np.matmul(np.conj(Psi_p.T), D), Psi_pw)
27+
p = np.squeeze(p)
28+
29+
# incident plane wave exhibiting infinite spatial bandwidth
30+
# p = np.exp(1j * k[:, np.newaxis]*r * np.cos(pol - pw_angle))
31+
32+
# plane wave decomposition using modal beamforming
2333
Bn = micarray.modal.radial.circular_pw(N, k, r, setup='open')
24-
Dn, _ = micarray.modal.radial.regularize(1/Bn, 100, 'softclip')
34+
Dn, _ = micarray.modal.radial.regularize(1/Bn, 3000, 'softclip')
2535
D = micarray.modal.radial.circ_diagonal_mode_mat(Dn)
26-
27-
# compute microphone signals for an incident broad-band plane wave
28-
p = np.exp(1j * k[:, np.newaxis]*r * np.cos(pol - pw_angle))
29-
# compute plane wave decomposition
36+
Psi_p = micarray.modal.angular.cht_matrix(N, pol, weights)
37+
Psi_q = micarray.modal.angular.cht_matrix(N, pol_pwd)
3038
A_pwd = np.matmul(np.matmul(np.conj(Psi_q.T), D), Psi_p)
3139
q_pwd = np.squeeze(np.matmul(A_pwd, np.expand_dims(p, 2)))
3240
q_pwd_t = np.fft.fftshift(np.fft.irfft(q_pwd, axis=0), axes=0)
3341

3442
# visualize plane wave decomposition (aka beampattern)
3543
plt.figure()
36-
plt.pcolormesh(k, pol_pwd/np.pi, micarray.util.db(q_pwd.T), vmin=-40)
44+
plt.pcolormesh(k, pol_pwd/np.pi, db(q_pwd.T), vmin=-40)
3745
plt.colorbar()
3846
plt.xlabel(r'$kr$')
3947
plt.ylabel(r'$\phi / \pi$')
4048
plt.title('Plane wave docomposition by modal beamformer (frequency domain)')
41-
plt.savefig('modal_open_beamformer_pwd_fd.png')
49+
plt.savefig('modal_circ_open_beamformer_pwd_fd.png')
4250

4351
plt.figure()
44-
plt.pcolormesh(range(2*len(k)-2), pol_pwd/np.pi, micarray.util.db(q_pwd_t.T), vmin=-40)
52+
plt.pcolormesh(range(2*len(k)-2), pol_pwd/np.pi, db(q_pwd_t.T), vmin=-40)
4553
plt.colorbar()
4654
plt.ylabel(r'$\phi / \pi$')
4755
plt.title('Plane wave docomposition by modal beamformer (time domain)')
48-
plt.savefig('modal_open_beamformer_pwd_td.png')
56+
plt.savefig('modal_circ_open_beamformer_pwd_td.png')
Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
"""
22
Compute the plane wave decomposition for an incident broadband plane wave
3-
on an rigid circular array using a modal beamformer of finite order.
3+
on a rigid circular array using a modal beamformer of finite order.
44
"""
55

66
import numpy as np
77
import matplotlib.pyplot as plt
88
import micarray
9+
from micarray.util import db
910

1011
Nsf = 50 # order of the incident sound field
1112
N = 30 # order of modal beamformer/microphone array
1213
pw_angle = 1 * np.pi # incidence angle of plane wave
13-
pol_pwd = np.linspace(0, 2*np.pi, 180, endpoint=False) # angles for plane wave decomposition
14+
pol_pwd = np.linspace(0, 2*np.pi, 180, endpoint=False) # angles for PWD
1415
k = np.linspace(0.1, 20, 100) # wavenumber vector
1516
r = 1 # radius of array
1617

1718
# get uniform grid (microphone positions) of order N
1819
pol, weights = micarray.modal.angular.grid_equal_polar_angle(N)
1920

2021
# pressure on the surface of a rigid cylinder for an incident plane wave
21-
bn = micarray.modal.radial.circular_pw(Nsf, k, r, setup='rigid')
22-
D = micarray.modal.radial.circ_diagonal_mode_mat(bn)
23-
Psi_p = micarray.modal.angular.cht_matrix(Nsf, pol, weights)
22+
Bn = micarray.modal.radial.circular_pw(Nsf, k, r, setup='rigid')
23+
D = micarray.modal.radial.circ_diagonal_mode_mat(Bn)
24+
Psi_p = micarray.modal.angular.cht_matrix(Nsf, pol)
2425
Psi_pw = micarray.modal.angular.cht_matrix(Nsf, pw_angle)
25-
p = np.matmul(np.matmul(np.conj(Psi_pw.T), D), Psi_p)
26+
p = np.matmul(np.matmul(np.conj(Psi_p.T), D), Psi_pw)
2627
p = np.squeeze(p)
2728

2829
# plane wave decomposition using modal beamforming
29-
Psi_p = micarray.modal.angular.cht_matrix(N, pol)
30+
Psi_p = micarray.modal.angular.cht_matrix(N, pol, weights)
3031
Psi_q = micarray.modal.angular.cht_matrix(N, pol_pwd)
3132
Bn = micarray.modal.radial.circular_pw(N, k, r, setup='rigid')
3233
Dn, _ = micarray.modal.radial.regularize(1/Bn, 100, 'softclip')
@@ -37,16 +38,16 @@
3738

3839
# visualize plane wave decomposition (aka beampattern)
3940
plt.figure()
40-
plt.pcolormesh(k, pol_pwd/np.pi, micarray.util.db(q_pwd.T), vmin=-40)
41+
plt.pcolormesh(k, pol_pwd/np.pi, db(q_pwd.T), vmin=-40)
4142
plt.colorbar()
4243
plt.xlabel(r'$kr$')
4344
plt.ylabel(r'$\phi / \pi$')
4445
plt.title('Plane wave docomposition by modal beamformer (frequency domain)')
45-
plt.savefig('modal_open_beamformer_pwd_fd.png')
46+
plt.savefig('modal_circ_open_beamformer_pwd_fd.png')
4647

4748
plt.figure()
48-
plt.pcolormesh(range(2*len(k)-2), pol_pwd/np.pi, micarray.util.db(q_pwd_t.T), vmin=-40)
49+
plt.pcolormesh(range(2*len(k)-2), pol_pwd/np.pi, db(q_pwd_t.T), vmin=-40)
4950
plt.colorbar()
5051
plt.ylabel(r'$\phi / \pi$')
5152
plt.title('Plane wave docomposition by modal beamformer (time domain)')
52-
plt.savefig('modal_open_beamformer_pwd_td.png')
53+
plt.savefig('modal_circ_open_beamformer_pwd_td.png')

micarray/modal/radial.py

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def circular_pw(N, k, r, setup):
239239
240240
.. math::
241241
242-
\mathring{P}_n(k) = i^{-n} b_n(kr)
242+
\mathring{P}_n(k) = i^n b_n(kr)
243243
244244
Parameters
245245
----------
@@ -254,14 +254,14 @@ def circular_pw(N, k, r, setup):
254254
255255
Returns
256256
-------
257-
bn : (M, N+1) numpy.ndarray
257+
bn : (M, 2*N+1) numpy.ndarray
258258
Radial weights for all orders up to N and the given wavenumbers.
259259
"""
260260
kr = util.asarray_1d(k*r)
261-
n = np.arange(N+1)
261+
n = np.roll(np.arange(-N, N+1), -N)
262262

263263
bn = circ_radial_weights(N, kr, setup)
264-
return (1j)**(n) * bn
264+
return 1j**n * bn
265265

266266

267267
def circular_ls(N, k, r, rs, setup):
@@ -289,20 +289,20 @@ def circular_ls(N, k, r, rs, setup):
289289
290290
Returns
291291
-------
292-
bn : (M, N+1) numpy.ndarray
292+
bn : (M, 2*N+1) numpy.ndarray
293293
Radial weights for all orders up to N and the given wavenumbers.
294294
"""
295295
k = util.asarray_1d(k)
296296
krs = k*rs
297-
n = np.arange(N+1)
297+
n = np.roll(np.arange(-N, N+1), -N)
298298

299299
bn = circ_radial_weights(N, k*r, setup)
300300
if len(k) == 1:
301301
bn = bn[np.newaxis, :]
302302
for i, x in enumerate(krs):
303303
Hn = special.hankel2(n, x)
304-
bn[i, :] = bn[i, :] * -1j/4 * Hn
305-
return np.squeeze(bn)
304+
bn[i, :] = bn[i, :] * Hn
305+
return -1j/4 * np.squeeze(bn)
306306

307307

308308
def circ_radial_weights(N, kr, setup):
@@ -327,7 +327,7 @@ def circ_radial_weights(N, kr, setup):
327327
328328
Returns
329329
-------
330-
bn : (M, N+1) numpy.ndarray
330+
bn : (M, 2*N+1) numpy.ndarray
331331
Radial weights for all orders up to N and the given wavenumbers.
332332
333333
"""
@@ -348,6 +348,7 @@ def circ_radial_weights(N, kr, setup):
348348
else:
349349
raise ValueError('setup must be either: open, card or rigid')
350350
Bns[i, :] = bn
351+
Bns = np.concatenate((Bns, (Bns*(-1)**np.arange(N+1))[:, :0:-1]), axis=-1)
351352
return np.squeeze(Bns)
352353

353354

@@ -366,35 +367,10 @@ def circ_diagonal_mode_mat(bk):
366367
Multidimensional array containing diagnonal matrices with input
367368
vector on main diagonal.
368369
"""
369-
bk = mirror_vec(bk)
370370
if len(bk.shape) == 1:
371371
bk = bk[np.newaxis, :]
372372
K, N = bk.shape
373373
Bk = np.zeros([K, N, N], dtype=complex)
374374
for k in range(K):
375375
Bk[k, :, :] = np.diag(bk[k, :])
376376
return np.squeeze(Bk)
377-
378-
379-
def mirror_vec(v):
380-
"""Mirror elements in a vector.
381-
382-
Returns a vector of length *2*len(v)-1* with symmetric elements.
383-
The first *len(v)* elements are the same as *v* and the last *len(v)-1*
384-
elements are *v[:0:-1]*. The function can be used to order the circular
385-
harmonic coefficients. If *v* is a matrix, it is treated as a stack of
386-
vectors residing in the last index and broadcast accordingly.
387-
388-
Parameters
389-
----------
390-
v : (, N+1) numpy.ndarray
391-
Input vector of stack of input vectors
392-
393-
Returns
394-
-------
395-
: (, 2*N+1) numpy.ndarray
396-
Vector of stack of vectors containing mirrored elements
397-
"""
398-
if len(v.shape) == 1:
399-
v = v[np.newaxis, :]
400-
return np.concatenate((v, v[:, :0:-1]), axis=1)

0 commit comments

Comments
 (0)