|
6 | 6 | "source": [ |
7 | 7 | "# WFS Referencing Schemes\n", |
8 | 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" |
| 9 | + "Illustrates the usage of the SFS toolbox for the simulation of different 2.5D WFS referencing schemes for contours within the listening area that exhibit amplitude correct synthesis, cf. Ch. 4.1.3 in [[Fir19]](https://last.hit.bme.hu/download/firtha/PhD_thesis/firtha_phd_thesis.pdf) and [[FFSS17]](doi:10.1109/TASLP.2017.2689245)" |
11 | 10 | ] |
12 | 11 | }, |
13 | 12 | { |
|
31 | 30 | "array = sfs.array.circular(N=64, R=R)\n", |
32 | 31 | "grid = sfs.util.xyz_grid([-2, 2], [-2, 2], 0, spacing=0.02)\n", |
33 | 32 | "\n", |
34 | | - "xs = -4, 0, 0 # point source on negative x-axis\n", |
| 33 | + "xs = sfs.util.asarray_of_rows((-4, 0, 0)) # point source on negative x-axis\n", |
35 | 34 | "normalize_gain = 4 * np.pi * np.linalg.norm(xs)\n", |
36 | 35 | "wavelength = 1 / 4 # m\n", |
37 | 36 | "f = sfs.default.c / wavelength # Hz\n", |
|
63 | 62 | " tapering_window,\n", |
64 | 63 | " size=0.125, ax=axs[i])\n", |
65 | 64 | " axs[i].plot(xref[:, 0][selection],\n", |
66 | | - " xref[:, 1][selection], 'C5o', ms=4)\n", |
| 65 | + " xref[:, 1][selection], 'o', color='dimgray', ms=4)\n", |
67 | 66 | " axs[i].grid(True)\n", |
68 | | - " plt.tight_layout()" |
| 67 | + " plt.tight_layout()\n", |
| 68 | + " return p\n" |
69 | 69 | ] |
70 | 70 | }, |
71 | 71 | { |
|
74 | 74 | "source": [ |
75 | 75 | "### Line as reference contour\n", |
76 | 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." |
| 77 | + "The reference contour is calculated according to [Fir19, eq. (52)] for a virtual point source on x-axis." |
78 | 78 | ] |
79 | 79 | }, |
80 | 80 | { |
|
85 | 85 | "source": [ |
86 | 86 | "# reference contour is a straight line\n", |
87 | 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", |
| 88 | + "# calc reference contour xref(x0), cf. [Fir19, eq. (24), (31), (52)]\n", |
| 89 | + "# this code assumes virtual point source on negative x-axis\n", |
| 90 | + "cosbeta = (array.n @ [1, 0, 0]).reshape(-1, 1)\n", |
| 91 | + "xref = array.x + (xs - array.x) * \\\n", |
| 92 | + " (xref_line + R * cosbeta) / (xs[0, 0] + R * cosbeta)\n", |
105 | 93 | "\n", |
106 | 94 | "d, selection, secondary_source = sfs.fd.wfs.point_25d(\n", |
107 | 95 | " 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)" |
| 96 | + "p_line = sound_field(d * normalize_gain, xref, selection,\n", |
| 97 | + " secondary_source, array, grid, tapering=False)" |
110 | 98 | ] |
111 | 99 | }, |
112 | 100 | { |
|
115 | 103 | "source": [ |
116 | 104 | "### Circle as reference contour\n", |
117 | 105 | "\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." |
| 106 | + "This reference contour is a circle with origin xs and a radius, such that the origin is on this circle. This contour is obtained with straightforward vector calculus." |
119 | 107 | ] |
120 | 108 | }, |
121 | 109 | { |
|
126 | 114 | "source": [ |
127 | 115 | "# reference contour is a circle with origin xs\n", |
128 | 116 | "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", |
| 117 | + "# calc reference contour xref(x0), cf. [Fir19, eq. (24), (31)]\n", |
| 118 | + "x0xs = array.x - xs\n", |
| 119 | + "x0xs_length = np.linalg.norm(x0xs, axis=1, keepdims=True)\n", |
| 120 | + "x0xs_unit = x0xs / x0xs_length # unit vec\n", |
| 121 | + "xref = xs + xref_dist * x0xs_unit\n", |
137 | 122 | "\n", |
138 | 123 | "d, selection, secondary_source = sfs.fd.wfs.point_25d(\n", |
139 | 124 | " 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)" |
| 125 | + "p_circ = sound_field(d * normalize_gain, xref, selection,\n", |
| 126 | + " secondary_source, array, grid, tapering=False)" |
| 127 | + ] |
| 128 | + }, |
| 129 | + { |
| 130 | + "cell_type": "code", |
| 131 | + "execution_count": null, |
| 132 | + "metadata": {}, |
| 133 | + "outputs": [], |
| 134 | + "source": [ |
| 135 | + "# (complex-valued) ratio between the two soundfields tells\n", |
| 136 | + "# us about the difference of both approaches\n", |
| 137 | + "sfs.plot2d.level(p_line / p_circ, grid, vmax=1, vmin=-1,\n", |
| 138 | + " cmap='seismic', colorbar_kwargs={'label': 'dB'})\n", |
| 139 | + "sfs.plot2d.loudspeakers(array.x, array.n, size=0.125)\n", |
| 140 | + "plt.grid(True)\n", |
| 141 | + "plt.tight_layout()" |
142 | 142 | ] |
143 | 143 | } |
144 | 144 | ], |
|
0 commit comments