"""Compute WFS driving functions.
.. include:: math-definitions.rst
.. plot::
:context: reset
import matplotlib.pyplot as plt
import numpy as np
import sfs
from scipy.signal import unit_impulse
# Plane wave
npw = sfs.util.direction_vector(np.radians(-45))
# Point source
xs = -1.5, 1.5, 0
rs = np.linalg.norm(xs) # distance from origin
ts = rs / sfs.default.c # time-of-arrival at origin
# Focused source
xf = -0.5, 0.5, 0
nf = sfs.util.direction_vector(np.radians(-45)) # normal vector
rf = np.linalg.norm(xf) # distance from origin
tf = rf / sfs.default.c # time-of-arrival at origin
# Impulsive excitation
fs = 44100
signal = unit_impulse(512), fs
# Circular loudspeaker array
N = 32 # number of loudspeakers
R = 1.5 # radius
array = sfs.array.circular(N, R)
grid = sfs.util.xyz_grid([-2, 2], [-2, 2], 0, spacing=0.02)
def plot(d, selection, secondary_source, t=0):
p = sfs.td.synthesize(d, selection, array, secondary_source, grid=grid,
observation_time=t)
sfs.plot2d.level(p, grid)
sfs.plot2d.loudspeakers(array.x, array.n, selection * array.a, size=0.15)
"""
import numpy as np
from numpy.core.umath_tests import inner1d # element-wise inner product
from .. import default
from .. import util
from . import secondary_source_point, apply_delays
[docs]def plane_25d(x0, n0, n=[0, 1, 0], xref=[0, 0, 0], c=None):
r"""Plane wave model by 2.5-dimensional WFS.
Parameters
----------
x0 : (N, 3) array_like
Sequence of secondary source positions.
n0 : (N, 3) array_like
Sequence of secondary source orientations.
n : (3,) array_like, optional
Normal vector (propagation direction) of synthesized plane wave.
xref : (3,) array_like, optional
Reference position
c : float, optional
Speed of sound
Returns
-------
delays : (N,) numpy.ndarray
Delays of secondary sources in seconds.
weights : (N,) numpy.ndarray
Weights of secondary sources.
selection : (N,) numpy.ndarray
Boolean array containing ``True`` or ``False`` depending on
whether the corresponding secondary source is "active" or not.
secondary_source_function : callable
A function that can be used to create the sound field of a
single secondary source. See `sfs.td.synthesize()`.
Notes
-----
2.5D correction factor
.. math::
g_0 = \sqrt{2 \pi |x_\mathrm{ref} - x_0|}
d using a plane wave as source model
.. math::
d_{2.5D}(x_0,t) = h(t)
2 g_0 \scalarprod{n}{n_0}
\dirac{t - \frac{1}{c} \scalarprod{n}{x_0}}
with wfs(2.5D) prefilter h(t), which is not implemented yet.
See :sfs:`d_wfs/#equation-td-wfs-plane-25d`
Examples
--------
.. plot::
:context: close-figs
delays, weights, selection, secondary_source = \
sfs.td.wfs.plane_25d(array.x, array.n, npw)
d = sfs.td.wfs.driving_signals(delays, weights, signal)
plot(d, selection, secondary_source)
"""
if c is None:
c = default.c
x0 = util.asarray_of_rows(x0)
n0 = util.asarray_of_rows(n0)
n = util.normalize_vector(n)
xref = util.asarray_1d(xref)
g0 = np.sqrt(2 * np.pi * np.linalg.norm(xref - x0, axis=1))
delays = inner1d(n, x0) / c
weights = 2 * g0 * inner1d(n, n0)
selection = util.source_selection_plane(n0, n)
return delays, weights, selection, secondary_source_point(c)
[docs]def point_25d(x0, n0, xs, xref=[0, 0, 0], c=None):
r"""Point source by 2.5-dimensional WFS.
Parameters
----------
x0 : (N, 3) array_like
Sequence of secondary source positions.
n0 : (N, 3) array_like
Sequence of secondary source orientations.
xs : (3,) array_like
Virtual source position.
xref : (3,) array_like, optional
Reference position
c : float, optional
Speed of sound
Returns
-------
delays : (N,) numpy.ndarray
Delays of secondary sources in seconds.
weights: (N,) numpy.ndarray
Weights of secondary sources.
selection : (N,) numpy.ndarray
Boolean array containing ``True`` or ``False`` depending on
whether the corresponding secondary source is "active" or not.
secondary_source_function : callable
A function that can be used to create the sound field of a
single secondary source. See `sfs.td.synthesize()`.
Notes
-----
2.5D correction factor
.. math::
g_0 = \sqrt{2 \pi |x_\mathrm{ref} - x_0|}
d using a point source as source model
.. math::
d_{2.5D}(x_0,t) = h(t)
\frac{g_0 \scalarprod{(x_0 - x_s)}{n_0}}
{2\pi |x_0 - x_s|^{3/2}}
\dirac{t - \frac{|x_0 - x_s|}{c}}
with wfs(2.5D) prefilter h(t), which is not implemented yet.
See :sfs:`d_wfs/#equation-td-wfs-point-25d`
Examples
--------
.. plot::
:context: close-figs
delays, weights, selection, secondary_source = \
sfs.td.wfs.point_25d(array.x, array.n, xs)
d = sfs.td.wfs.driving_signals(delays, weights, signal)
plot(d, selection, secondary_source, t=ts)
"""
if c is None:
c = default.c
x0 = util.asarray_of_rows(x0)
n0 = util.asarray_of_rows(n0)
xs = util.asarray_1d(xs)
xref = util.asarray_1d(xref)
g0 = np.sqrt(2 * np.pi * np.linalg.norm(xref - x0, axis=1))
ds = x0 - xs
r = np.linalg.norm(ds, axis=1)
delays = r/c
weights = g0 * inner1d(ds, n0) / (2 * np.pi * r**(3/2))
selection = util.source_selection_point(n0, x0, xs)
return delays, weights, selection, secondary_source_point(c)
[docs]def focused_25d(x0, n0, xs, ns, xref=[0, 0, 0], c=None):
r"""Point source by 2.5-dimensional WFS.
Parameters
----------
x0 : (N, 3) array_like
Sequence of secondary source positions.
n0 : (N, 3) array_like
Sequence of secondary source orientations.
xs : (3,) array_like
Virtual source position.
ns : (3,) array_like
Normal vector (propagation direction) of focused source.
This is used for secondary source selection,
see `sfs.util.source_selection_focused()`.
xref : (3,) array_like, optional
Reference position
c : float, optional
Speed of sound
Returns
-------
delays : (N,) numpy.ndarray
Delays of secondary sources in seconds.
weights: (N,) numpy.ndarray
Weights of secondary sources.
selection : (N,) numpy.ndarray
Boolean array containing ``True`` or ``False`` depending on
whether the corresponding secondary source is "active" or not.
secondary_source_function : callable
A function that can be used to create the sound field of a
single secondary source. See `sfs.td.synthesize()`.
Notes
-----
2.5D correction factor
.. math::
g_0 = \sqrt{\frac{|x_\mathrm{ref} - x_0|}
{|x_0-x_s| + |x_\mathrm{ref}-x_0|}}
d using a point source as source model
.. math::
d_{2.5D}(x_0,t) = h(t)
\frac{g_0 \scalarprod{(x_0 - x_s)}{n_0}}
{|x_0 - x_s|^{3/2}}
\dirac{t + \frac{|x_0 - x_s|}{c}}
with wfs(2.5D) prefilter h(t), which is not implemented yet.
See :sfs:`d_wfs/#equation-td-wfs-focused-25d`
Examples
--------
.. plot::
:context: close-figs
delays, weights, selection, secondary_source = \
sfs.td.wfs.focused_25d(array.x, array.n, xf, nf)
d = sfs.td.wfs.driving_signals(delays, weights, signal)
plot(d, selection, secondary_source, t=tf)
"""
if c is None:
c = default.c
x0 = util.asarray_of_rows(x0)
n0 = util.asarray_of_rows(n0)
xs = util.asarray_1d(xs)
xref = util.asarray_1d(xref)
ds = x0 - xs
r = np.linalg.norm(ds, axis=1)
g0 = np.sqrt(np.linalg.norm(xref - x0, axis=1)
/ (np.linalg.norm(xref - x0, axis=1) + r))
delays = -r/c
weights = g0 * inner1d(ds, n0) / (2 * np.pi * r**(3/2))
selection = util.source_selection_focused(ns, x0, xs)
return delays, weights, selection, secondary_source_point(c)
[docs]def driving_signals(delays, weights, signal):
"""Get driving signals per secondary source.
Returned signals are the delayed and weighted mono input signal
(with N samples) per channel (C).
Parameters
----------
delays : (C,) array_like
Delay in seconds for each channel, negative values allowed.
weights : (C,) array_like
Amplitude weighting factor for each channel.
signal : (N,) array_like + float
Excitation signal consisting of (mono) audio data and a sampling
rate (in Hertz). A `DelayedSignal` object can also be used.
Returns
-------
`DelayedSignal`
A tuple containing the driving signals (in a `numpy.ndarray`
with shape ``(N, C)``), followed by the sampling rate (in Hertz)
and a (possibly negative) time offset (in seconds).
"""
delays = util.asarray_1d(delays)
weights = util.asarray_1d(weights)
data, samplerate, signal_offset = apply_delays(signal, delays)
return util.DelayedSignal(data * weights, samplerate, signal_offset)