Skip to content

Commit bc4d6c9

Browse files
committed
move example into dedicated jupyter notebook
- more convenient to have this in a notebbok - the wfs.py examples remain minimal and straightforward - the proposed notebook can be extended to referencing schemes for linear SSDs and in general for other potential referencing schemes
1 parent 84435bd commit bc4d6c9

File tree

3 files changed

+167
-106
lines changed

3 files changed

+167
-106
lines changed

doc/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Examples
1414
:maxdepth: 1
1515

1616
examples/sound-field-synthesis
17+
examples/wfs-referencing
1718
examples/modal-room-acoustics
1819
examples/mirror-image-source-model
1920
examples/animations-pulsating-sphere

doc/examples/wfs-referencing.ipynb

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# WFS Referencing Schemes\n",
8+
"\n",
9+
"Illustrates the usage of the SFS toolbox for the simulation of different sound fields using 2.5D WFS and referencing schemes for contours that exhibit amplitude correct synthesis, cf. Ch. 4.1.3 in Gergely Firtha's doctoral thesis [A Generalized Wave Field Synthesis Framework with Application for Moving Virtual Sources](https://last.hit.bme.hu/download/firtha/PhD_thesis/firtha_phd_thesis.pdf) of 2019.\n",
10+
"\n"
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"metadata": {},
16+
"source": [
17+
"## Circular loudspeaker arrays"
18+
]
19+
},
20+
{
21+
"cell_type": "code",
22+
"execution_count": null,
23+
"metadata": {},
24+
"outputs": [],
25+
"source": [
26+
"import matplotlib.pyplot as plt\n",
27+
"import numpy as np\n",
28+
"import sfs\n",
29+
"\n",
30+
"R = 1.5 # Radius of circular loudspeaker array\n",
31+
"array = sfs.array.circular(N=64, R=R)\n",
32+
"grid = sfs.util.xyz_grid([-2, 2], [-2, 2], 0, spacing=0.02)\n",
33+
"\n",
34+
"xs = -4, 0, 0 # point source on negative x-axis\n",
35+
"normalize_gain = 4 * np.pi * np.linalg.norm(xs)\n",
36+
"lmb = 1 / 4 # m\n",
37+
"f = sfs.default.c / lmb # Hz\n",
38+
"omega = 2 * np.pi * f # rad/s"
39+
]
40+
},
41+
{
42+
"cell_type": "code",
43+
"execution_count": null,
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"def sound_field(d, xref, selection,\n",
48+
" secondary_source, array, grid, tapering=True):\n",
49+
" if tapering:\n",
50+
" tapering_window = sfs.tapering.kaiser(selection, beta=1)\n",
51+
" else:\n",
52+
" tapering_window = sfs.tapering.none(selection)\n",
53+
"\n",
54+
" p = sfs.fd.synthesize(d, tapering_window,\n",
55+
" array, secondary_source, grid=grid)\n",
56+
"\n",
57+
" fig, axs = plt.subplots(1, 2, figsize=(10, 8))\n",
58+
" sfs.plot2d.amplitude(p, grid, vmax=2, vmin=-2, ax=axs[0])\n",
59+
" sfs.plot2d.level(p, grid, vmax=6, vmin=-6, ax=axs[1],\n",
60+
" cmap='seismic', colorbar_kwargs={'label': 'dB'})\n",
61+
" for i in range(axs.shape[0]):\n",
62+
" sfs.plot2d.loudspeakers(array.x, array.n,\n",
63+
" tapering_window,\n",
64+
" size=0.125, ax=axs[i])\n",
65+
" axs[i].plot(xref[:, 0][selection],\n",
66+
" xref[:, 1][selection], 'C5o', ms=4)\n",
67+
" axs[i].grid(True)\n",
68+
" plt.tight_layout()"
69+
]
70+
},
71+
{
72+
"cell_type": "markdown",
73+
"metadata": {},
74+
"source": [
75+
"### Line as reference contour\n",
76+
"\n",
77+
"The reference contour is calculated according to https://github.com/spatialaudio/wfs_chapter_hda/blob/master/python/wfs25d_circSSD.py#L91 for a virtual point source on x-axis."
78+
]
79+
},
80+
{
81+
"cell_type": "code",
82+
"execution_count": null,
83+
"metadata": {},
84+
"outputs": [],
85+
"source": [
86+
"# reference contour is a straight line\n",
87+
"xref_line = 0\n",
88+
"# calc reference contour xref(x0):\n",
89+
"x0_tmp = array.x.T[np.newaxis, :]\n",
90+
"xs_tmp = np.array(xs)[np.newaxis, :, np.newaxis]\n",
91+
"x0xs = x0_tmp - xs_tmp\n",
92+
"x0xs_length = np.linalg.norm(x0xs, axis=1)\n",
93+
"x0xs_unit = x0xs / x0xs_length\n",
94+
"n0_ = array.n.T[np.newaxis, :]\n",
95+
"xref = np.zeros_like(x0_tmp)\n",
96+
"# code assumes that virtual point source is on x-axis:\n",
97+
"for i in range(array.x.shape[0]):\n",
98+
" cosbeta = np.dot(-n0_[0, :, i], [-1, 0, 0]) # use outward SSD normal\n",
99+
" tmp = x0xs_unit[0, :, i]\n",
100+
" tmp *= -x0xs_length[0, i]\n",
101+
" tmp *= xref_line + R * cosbeta\n",
102+
" tmp /= xs_tmp[0, 0, 0] + R * cosbeta\n",
103+
" xref[0, :, i] = x0_tmp[0, :, i] + tmp\n",
104+
"xref = np.squeeze(xref).T\n",
105+
"\n",
106+
"d, selection, secondary_source = sfs.fd.wfs.point_25d(\n",
107+
" omega, array.x, array.n, xs, xref=xref)\n",
108+
"sound_field(d * normalize_gain, xref, selection,\n",
109+
" secondary_source, array, grid, tapering=False)"
110+
]
111+
},
112+
{
113+
"cell_type": "markdown",
114+
"metadata": {},
115+
"source": [
116+
"### Circle as reference contour\n",
117+
"\n",
118+
"This reference contour is a circle with origin xs and a radius, such that the origin is on this circle. This contour is straightforward with some obvious vector calculus."
119+
]
120+
},
121+
{
122+
"cell_type": "code",
123+
"execution_count": null,
124+
"metadata": {},
125+
"outputs": [],
126+
"source": [
127+
"# reference contour is a circle with origin xs\n",
128+
"xref_dist = np.linalg.norm(xs)\n",
129+
"# calc reference contour xref(x0):\n",
130+
"x0_tmp = array.x.T[np.newaxis, :]\n",
131+
"xs_tmp = np.array(xs)[np.newaxis, :, np.newaxis]\n",
132+
"x0xs = x0_tmp - xs_tmp\n",
133+
"x0xs_length = np.linalg.norm(x0xs, axis=1)\n",
134+
"x0xs_unit = x0xs / x0xs_length\n",
135+
"xref = x0_tmp + (xref_dist - x0xs_length) * x0xs_unit\n",
136+
"xref = np.squeeze(xref).T\n",
137+
"\n",
138+
"d, selection, secondary_source = sfs.fd.wfs.point_25d(\n",
139+
" omega, array.x, array.n, xs, xref=xref)\n",
140+
"sound_field(d * normalize_gain, xref, selection,\n",
141+
" secondary_source, array, grid, tapering=False)"
142+
]
143+
}
144+
],
145+
"metadata": {
146+
"kernelspec": {
147+
"display_name": "sfs",
148+
"language": "python",
149+
"name": "python3"
150+
},
151+
"language_info": {
152+
"codemirror_mode": {
153+
"name": "ipython",
154+
"version": 3
155+
},
156+
"file_extension": ".py",
157+
"mimetype": "text/x-python",
158+
"name": "python",
159+
"nbconvert_exporter": "python",
160+
"pygments_lexer": "ipython3",
161+
"version": "3.13.7"
162+
}
163+
},
164+
"nbformat": 4,
165+
"nbformat_minor": 2
166+
}

sfs/fd/wfs.py

Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -218,112 +218,6 @@ def point_25d(omega, x0, n0, xs, xref=[0, 0, 0], c=None, omalias=None):
218218
normalize_gain = 4 * np.pi * np.linalg.norm(xs)
219219
plot(normalize_gain * d, selection, secondary_source)
220220
221-
.. plot::
222-
:context: close-figs
223-
224-
xs = -4, 0, 0 # point source on negative x-axis
225-
xref_line = 0 # reference contour is a straight line
226-
227-
lmb = 1 / 4 # m
228-
f_tmp = sfs.default.c / lmb # Hz
229-
omega_tmp = 2 * np.pi * f_tmp # rad/s
230-
231-
# calc reference contour xref(x0):
232-
x0_tmp = array2.x.T[np.newaxis, :]
233-
xs_tmp = np.array(xs)[np.newaxis, :, np.newaxis]
234-
x0xs = x0_tmp - xs_tmp
235-
x0xs_length = np.linalg.norm(x0xs, axis=1)
236-
x0xs_unit = x0xs / x0xs_length
237-
n0_ = array2.n.T[np.newaxis, :]
238-
xref = np.zeros_like(x0_tmp)
239-
# cf. https://github.com/spatialaudio/wfs_chapter_hda/blob/master/python/wfs25d_circSSD.py#L91 # noqa
240-
# code is valid for a virtual point source on negative x-axis:
241-
for i in range(array2.x.shape[0]):
242-
cosbeta = np.dot(-n0_[0, :, i], [-1, 0, 0]) # use outward SSD normal
243-
tmp = x0xs_unit[0, :, i]
244-
tmp *= -x0xs_length[0, i]
245-
tmp *= xref_line + R * cosbeta
246-
tmp /= xs_tmp[0, 0, 0] + R * cosbeta
247-
xref[0, :, i] = x0_tmp[0, :, i] + tmp
248-
xref = np.squeeze(xref).T
249-
250-
d, selection, secondary_source = sfs.fd.wfs.point_25d(
251-
omega_tmp, array2.x, array2.n, xs, xref=xref)
252-
normalize_gain = 4 * np.pi * np.linalg.norm(xs)
253-
p = sfs.fd.synthesize(d * normalize_gain,
254-
selection,
255-
array2,
256-
secondary_source,
257-
grid=grid)
258-
259-
plt.rcParams['figure.figsize'] = 10, 8
260-
ax_amp, ax_lvl = plt.subplot(2, 2, 1), plt.subplot(2, 2, 2)
261-
sfs.plot2d.amplitude(p, grid, vmax=2, vmin=-2, ax=ax_amp,
262-
colorbar_kwargs={'label': 'lin'})
263-
sfs.plot2d.level(p, grid, vmax=6, vmin=-6,
264-
cmap='seismic', ax=ax_lvl,
265-
colorbar_kwargs={'label': 'dB'})
266-
sfs.plot2d.loudspeakers(array2.x, array2.n,
267-
selection * array2.a,
268-
size=0.125, ax=ax_amp)
269-
sfs.plot2d.loudspeakers(array2.x, array2.n,
270-
selection * array2.a,
271-
size=0.125, ax=ax_lvl)
272-
# plot xref(x0) contour:
273-
ax_amp.plot(xref[:, 0][selection],
274-
xref[:, 1][selection], 'C5o', ms=4)
275-
ax_lvl.plot(xref[:, 0][selection],
276-
xref[:, 1][selection], 'C5o', ms=4)
277-
ax_amp.grid(True), ax_lvl.grid(True)
278-
279-
.. plot::
280-
:context: close-figs
281-
282-
xs = -4, 0, 0 # point source on negative x-axis
283-
xref_dist = 4 # radius for reference contour circle with origin xs
284-
285-
lmb = 1 / 4 # m
286-
f_tmp = sfs.default.c / lmb # Hz
287-
omega_tmp = 2 * np.pi * f_tmp # rad/s
288-
289-
# calc reference contour xref(x0):
290-
x0_tmp = array2.x.T[np.newaxis, :]
291-
xs_tmp = np.array(xs)[np.newaxis, :, np.newaxis]
292-
x0xs = x0_tmp - xs_tmp
293-
x0xs_length = np.linalg.norm(x0xs, axis=1)
294-
x0xs_unit = x0xs / x0xs_length
295-
xref = x0_tmp + (xref_dist - x0xs_length) * x0xs_unit
296-
xref = np.squeeze(xref).T
297-
298-
d, selection, secondary_source = sfs.fd.wfs.point_25d(
299-
omega_tmp, array2.x, array2.n, xs, xref=xref)
300-
normalize_gain = 4 * np.pi * np.linalg.norm(xs)
301-
p = sfs.fd.synthesize(d * normalize_gain,
302-
selection,
303-
array2,
304-
secondary_source,
305-
grid=grid)
306-
307-
plt.rcParams['figure.figsize'] = 10, 8
308-
ax_amp, ax_lvl = plt.subplot(2, 2, 1), plt.subplot(2, 2, 2)
309-
sfs.plot2d.amplitude(p, grid, vmax=2, vmin=-2, ax=ax_amp,
310-
colorbar_kwargs={'label': 'lin'})
311-
sfs.plot2d.level(p, grid, vmax=6, vmin=-6,
312-
cmap='seismic', ax=ax_lvl,
313-
colorbar_kwargs={'label': 'dB'})
314-
sfs.plot2d.loudspeakers(array2.x, array2.n,
315-
selection * array2.a,
316-
size=0.125, ax=ax_amp)
317-
sfs.plot2d.loudspeakers(array2.x, array2.n,
318-
selection * array2.a,
319-
size=0.125, ax=ax_lvl)
320-
# plot xref(x0) contour:
321-
ax_amp.plot(xref[:, 0][selection],
322-
xref[:, 1][selection], 'C5o', ms=4)
323-
ax_lvl.plot(xref[:, 0][selection],
324-
xref[:, 1][selection], 'C5o', ms=4)
325-
ax_amp.grid(True), ax_lvl.grid(True)
326-
327221
"""
328222
x0 = _util.asarray_of_rows(x0)
329223
n0 = _util.asarray_of_rows(n0)

0 commit comments

Comments
 (0)