Example #1
0
    def __init__(self, x, y, z, listener_position=None):
        """
        Parameters
        ----------
        x : array_like
        y : array_like
        z : array_like
        listener_position : (3,), cartesian, optional
            Offset, will be substracted from the loudspeaker positions.

        """
        self.x = utils.asarray_1d(x)
        self.y = utils.asarray_1d(y)
        self.z = utils.asarray_1d(z)
        if listener_position is None:
            listener_position = [0, 0, 0]
        self.listener_position = utils.asarray_1d(listener_position)

        # Listener position as origin
        self.x -= listener_position[0]
        self.y -= listener_position[1]
        self.z -= listener_position[2]
        # TODO: Better handling of this, e.g. not effective when updating hull:
        self.listener_position -= self.listener_position
        _, _, self.d = utils.cart2sph(self.x, self.y, self.z)

        # amplitude decay exponent
        self.a = 1

        # Triangulation of points
        _hull = get_hull(self.x, self.y, self.z)
        self.points = _hull.points
        self.npoints = _hull.npoints
        self.nsimplex = _hull.nsimplex
        self.vertices = _hull.vertices
        self.simplices = _hull.simplices
        self.simplices = sort_vertices(self.simplices)
        self.centroids = calculate_centroids(self)
        self.face_areas = calculate_face_areas(self)
        self.face_normals = calculate_face_normals(self)
        self.vertex_normals = calculate_vertex_normals(self)
        self.barycenter = calculate_barycenter(self)

        # All simplices enclosing listener valid per default for rendering
        self._encloses_listener = check_listener_inside(
            self, self.listener_position)
        self.valid_simplices = self._encloses_listener

        # see 'ambisonics_setup()'
        self.ambisonics_hull = []
        self.kernel_hull = []
        self.characteristic_order = None

        # some checks
        assert (len(self.d) == self.npoints)
Example #2
0
def mad(F_nm, hull, N_sph=None):
    """Loudspeaker signals of Mode-Matching Ambisonic Decoder.

    Parameters
    ----------
    F_nm : ((N_sph+1)**2, S) numpy.ndarray
        Matrix of spherical harmonics coefficients of spherical function(S).
    hull : LoudspeakerSetup
    N_sph : int
        Decoding order, defaults to hull.characteristic_order.

    Returns
    -------
    ls_sig : (L, S) numpy.ndarray
        Loudspeaker L output signal S.

    References
    ----------
    ch. 4.9.2, Zotter, F., & Frank, M. (2019). Ambisonics.
    Springer Topics in Signal Processing.

    Examples
    --------
    .. plot::
        :context: close-figs

        ls_setup = spa.decoder.LoudspeakerSetup(ls_x, ls_y, ls_z)
        ls_setup.pop_triangles(normal_limit=85, aperture_limit=90,
                               opening_limit=150)

        spa.plots.decoder_performance(ls_setup, 'MAD')

    """
    if N_sph is None:
        if hull.characteristic_order:
            N_sph = hull.characteristic_order
        else:
            N_sph = hull.get_characteristic_order()

    L = hull.npoints
    N_sph_in = int(np.sqrt(F_nm.shape[0]) - 1)
    assert (N_sph_in >= N_sph)  # for now

    ls_azi, ls_colat, ls_r = utils.cart2sph(*hull.points.T)
    Y_ls = sph.sh_matrix(N_sph, ls_azi, ls_colat, SH_type='real')

    D = (np.linalg.pinv(Y_ls)).T
    D *= np.sqrt(L / (N_sph + 1)**2)  # Energy to unity (on t-design)

    # loudspeaker output signals
    ls_sig = D @ F_nm[:(N_sph + 1)**2, :]
    return ls_sig
Example #3
0
    def binauralize(self, ls_signals, fs, orientation=(0, 0), hrirs=None):
        """Create binaural signals that the loudspeaker signals produce on this
        setup (no delays).

        Parameters
        ----------
        ls_signals : (L, S) np.ndarray
            Loudspeaker signals.
        fs : int
        orientation : (azi, colat) tuple, optional
            Listener orientation offset (azimuth, colatitude) in rad.
        hrirs : sig.HRIRs, optional

        Returns
        -------
        l_sig : array_like
        r_sig : array_like

        """
        if hrirs is None:
            hrirs = IO.load_hrirs(fs)
        assert (hrirs.fs == fs)
        ls_signals = np.atleast_2d(ls_signals)
        assert ls_signals.shape[0] == self.npoints, \
            'Provide signal per loudspeaker!'
        # distance attenuation
        relative_position = self.points - \
                            self.listener_position
        ls_azi, ls_colat, ls_r = utils.cart2sph(*relative_position.T)
        ls_signals = np.diag(1 / ls_r**self.a) @ ls_signals
        # convolve with hrir
        l_sig = np.zeros(ls_signals.shape[1] + len(hrirs) - 1)
        r_sig = np.zeros_like(l_sig)
        for ch, ls_sig in enumerate(ls_signals):
            if any(abs(ls_sig) > 10e-6):  # Gate at -100dB
                hrir_l, hrir_r = hrirs.nearest_hrirs(
                    ls_azi[ch] - orientation[0], ls_colat[ch] - orientation[1])
                # sum all loudspeakers
                l_sig += signal.convolve(ls_sig, hrir_l)
                r_sig += signal.convolve(ls_sig, hrir_r)
        return l_sig, r_sig
Example #4
0
    blacklist = None
    ls_setup = IO.load_layout("../data/ls_layouts/Graz.json",
                              listener_position=listener_position)
    ls_setup.pop_triangles(normal_limit, aperture_limit, opening_limit,
                           blacklist)

else:
    raise ValueError

# %% Show setup
ls_setup.show()
plots.hull_normals(ls_setup)

# Test source location
src = np.array([1, 0.5, 2.5])
src_azi, src_colat, _ = utils.cart2sph(*src.tolist())

# %% VBAP
gains_vbap = decoder.vbap(src, ls_setup)

# %% Ambisonic decoding
# Ambisonic setup
N_e = ls_setup.get_characteristic_order()
ls_setup.ambisonics_setup(update_hull=True, N_kernel=20)

# Show ALLRAP hulls
plots.hull(ls_setup.ambisonics_hull, title='Ambisonic hull')
plots.hull(ls_setup.kernel_hull, mark_invalid=False, title='Kernel hull')

# ALLRAP
gains_allrap = decoder.allrap(src, ls_setup, N_sph=N_e)
Example #5
0
#     version: 3.6.8
# ---

# %%
import numpy as np
import matplotlib.pyplot as plt

from spaudiopy import utils, IO, sph, plots, grids

# %% Spherical Harmonics Order
N = 1

# %%
# Grids
t = grids.load_t_design(2)
t_az, t_colat, t_r = utils.cart2sph(t[:, 0], t[:, 1], t[:, 2])
azi = t_az
colat = t_colat

# %%
# First, check condition number to which SH order the SHT is stable
# Tetraeder is not suited for SHT N>1:
sph.check_cond_sht(3, t_az, t_colat, 'real')

# %% Real and Complex SHs
Y_nm_c = sph.sh_matrix(N, azi, colat, 'complex')
Y_nm_r = sph.sh_matrix(N, azi, colat, 'real')

# %%
# Look at some SHTs
sig = np.array([1, 1, 1, 1])
Example #6
0
def allrad2(F_nm, hull, N_sph=None, jobs_count=1):
    """Loudspeaker signals of All-Round Ambisonic Decoder 2.

    Parameters
    ----------
    F_nm : ((N_sph+1)**2, S) numpy.ndarray
        Matrix of spherical harmonics coefficients of spherical function(S).
    hull : LoudspeakerSetup
    N_sph : int
        Decoding order, defaults to hull.characteristic_order.
    jobs_count : int or None, optional
        Number of parallel jobs, 'None' employs 'cpu_count'.

    Returns
    -------
    ls_sig : (L, S) numpy.ndarray
        Loudspeaker L output signal S.

    References
    ----------
    Zotter, F., & Frank, M. (2018). Ambisonic decoding with panning-invariant
    loudness on small layouts (AllRAD2). In 144th AES Convention.

    Examples
    --------
    .. plot::
        :context: close-figs

        ls_setup = spa.decoder.LoudspeakerSetup(ls_x, ls_y, ls_z)
        ls_setup.pop_triangles(normal_limit=85, aperture_limit=90,
                               opening_limit=150)
        ls_setup.ambisonics_setup(update_hull=True)

        spa.plots.decoder_performance(ls_setup, 'ALLRAD2')

    """
    if not hull.ambisonics_hull:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if hull.kernel_hull:
        kernel_hull = hull.kernel_hull
    else:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if N_sph is None:
        N_sph = hull.characteristic_order

    N_sph_in = int(np.sqrt(F_nm.shape[0]) - 1)
    assert (N_sph == N_sph_in)  # for now
    if N_sph_in > kernel_hull.N_kernel:
        warn("Undersampling the sphere. Needs higher N_Kernel.")

    # virtual t-design loudspeakers
    J = len(kernel_hull.points)
    # virtual speakers expressed as phantom sources (Kernel)
    G_k = allrap2(src=kernel_hull.points,
                  hull=hull,
                  N_sph=N_sph,
                  jobs_count=jobs_count)
    # tapering already applied in kernel, sufficient?

    # virtual Ambisonic decoder
    _k_azi, _k_colat, _k_r = utils.cart2sph(kernel_hull.points[:, 0],
                                            kernel_hull.points[:, 1],
                                            kernel_hull.points[:, 2])
    # band-limited Dirac
    Y_bld = sph.sh_matrix(N_sph, _k_azi, _k_colat, SH_type='real')

    # ALLRAD2 Decoder
    D = 4 * np.pi / J * G_k.T @ Y_bld

    # loudspeaker output signals
    ls_sig = D @ F_nm
    return ls_sig
Example #7
0
def allrad(F_nm, hull, N_sph=None, jobs_count=1):
    """Loudspeaker signals of All-Round Ambisonic Decoder.

    Parameters
    ----------
    F_nm : ((N_sph+1)**2, S) numpy.ndarray
        Matrix of spherical harmonics coefficients of spherical function(S).
    hull : LoudspeakerSetup
    N_sph : int
        Decoding order.
    jobs_count : int or None, optional
        Number of parallel jobs, 'None' employs 'cpu_count'.

    Returns
    -------
    ls_sig : (L, S) numpy.ndarray
        Loudspeaker L output signal S.

    References
    ----------
    Zotter, F., & Frank, M. (2012). All-Round Ambisonic Panning and Decoding.
    Journal of Audio Engineering Society, Sec. 6.

    Examples
    --------
    .. plot::
        :context: close-figs

        ls_setup = spa.decoder.LoudspeakerSetup(ls_x, ls_y, ls_z)
        ls_setup.pop_triangles(normal_limit=85, aperture_limit=90,
                               opening_limit=150)
        ls_setup.ambisonics_setup(update_hull=True)

        spa.plots.decoder_performance(ls_setup, 'ALLRAD')

    """
    if hull.ambisonics_hull:
        ambisonics_hull = hull.ambisonics_hull
    else:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if hull.kernel_hull:
        kernel_hull = hull.kernel_hull
    else:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if N_sph is None:
        N_sph = hull.characteristic_order

    N_sph_in = int(np.sqrt(F_nm.shape[0]) - 1)
    assert (N_sph == N_sph_in)  # for now
    if N_sph_in > kernel_hull.N_kernel:
        warn("Undersampling the sphere. Needs higher N_Kernel.")

    # virtual t-design loudspeakers
    J = len(kernel_hull.points)
    # virtual speakers expressed as VBAP phantom sources (Kernel)
    G_k = vbap(src=kernel_hull.points,
               hull=ambisonics_hull,
               jobs_count=jobs_count)

    # SH tapering coefficients
    a_n = sph.max_rE_weights(N_sph)
    a_n = sph.unity_gain(a_n)
    a_nm = sph.repeat_per_order(a_n)

    # virtual Ambisonic decoder
    _k_azi, _k_colat, _k_r = utils.cart2sph(kernel_hull.points[:, 0],
                                            kernel_hull.points[:, 1],
                                            kernel_hull.points[:, 2])
    # band-limited Dirac
    Y_bld = sph.sh_matrix(N_sph, _k_azi, _k_colat, SH_type='real')

    # ALLRAD Decoder
    D = 4 * np.pi / J * G_k.T @ Y_bld
    # apply tapering to decoder matrix
    D = D @ np.diag(a_nm)

    # remove imaginary loudspeakers
    if ambisonics_hull.imaginary_ls_idx is not None:
        D = np.delete(D, ambisonics_hull.imaginary_ls_idx, axis=0)

    # loudspeaker output signals
    ls_sig = D @ F_nm

    return ls_sig
Example #8
0
def allrap2(src, hull, N_sph=None, jobs_count=1):
    """Loudspeaker gains for All-Round Ambisonic Panning 2.

    Parameters
    ----------
    src : (N, 3)
        Cartesian coordinates of N sources to be rendered.
    hull : LoudspeakerSetup
    N_sph : int
        Decoding order, defaults to hull.characteristic_order.
    jobs_count : int or None, optional
        Number of parallel jobs, 'None' employs 'cpu_count'.

    Returns
    -------
    gains : (N, L) numpy.ndarray
        Panning gains for L loudspeakers to render N sources.

    References
    ----------
    Zotter, F., & Frank, M. (2018). Ambisonic decoding with panning-invariant
    loudness on small layouts (AllRAD2). In 144th AES Convention.

    Examples
    --------
    .. plot::
        :context: close-figs

        ls_setup = spa.decoder.LoudspeakerSetup(ls_x, ls_y, ls_z)
        ls_setup.pop_triangles(normal_limit=85, aperture_limit=90,
                               opening_limit=150)
        ls_setup.ambisonics_setup(update_hull=True)

        spa.plots.decoder_performance(ls_setup, 'ALLRAP2')

    """
    if hull.ambisonics_hull:
        ambisonics_hull = hull.ambisonics_hull
    else:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if hull.kernel_hull:
        kernel_hull = hull.kernel_hull
    else:
        raise ValueError('Run LoudspeakerSetup.ambisonics_setup() first!')
    if N_sph is None:
        N_sph = hull.characteristic_order

    src = np.atleast_2d(src)
    assert (src.shape[1] == 3)

    # normalize direction
    src = src / np.linalg.norm(src, axis=1)[:, np.newaxis]
    # virtual t-design loudspeakers
    J = len(kernel_hull.points)
    # virtual speakers expressed as VBAP phantom sources (Kernel)
    G_k = vbap(src=kernel_hull.points,
               hull=ambisonics_hull,
               jobs_count=jobs_count)

    # SH tapering coefficients
    a_n = sph.max_rE_weights(N_sph)
    a_n = sph.unity_gain(a_n)
    # sqrt(E) normalization (eq.6)
    a_w = np.sqrt(
        np.sum((2 * (np.arange(N_sph + 1) + 1)) / (4 * np.pi) * a_n**2))
    a_n /= a_w
    a_nm = sph.repeat_per_order(a_n)

    # sources
    _s_azi, _s_colat, _s_r = utils.cart2sph(src[:, 0], src[:, 1], src[:, 2])
    Y_s = sph.sh_matrix(N_sph, _s_azi, _s_colat, SH_type='real')
    # kernel
    _k_azi, _k_colat, _k_r = utils.cart2sph(kernel_hull.points[:, 0],
                                            kernel_hull.points[:, 1],
                                            kernel_hull.points[:, 2])
    Y_k = sph.sh_matrix(N_sph, _k_azi, _k_colat, SH_type='real')

    # discretized (band-limited) ambisonic panning function
    G_bld = Y_s @ np.diag(a_nm) @ Y_k.T

    # remove imaginary loudspeaker
    if ambisonics_hull.imaginary_ls_idx is not None:
        G_k = np.delete(G_k, ambisonics_hull.imaginary_ls_idx, axis=1)

    gains = np.sqrt(4 * np.pi / J * G_bld**2 @ G_k**2)
    return gains
Example #9
0
def epad(F_nm, hull, N_sph=None):
    r"""Loudspeaker signals of Energy-Preserving Ambisonic Decoder.

    Parameters
    ----------
    F_nm : ((N_sph+1)**2, S) numpy.ndarray
        Matrix of spherical harmonics coefficients of spherical function(S).
    hull : LoudspeakerSetup
    N_sph : int
        Decoding order, defaults to hull.characteristic_order.

    Returns
    -------
    ls_sig : (L, S) numpy.ndarray
        Loudspeaker L output signal S.

    Notes
    -----
    Number of loudspeakers should be greater or equal than SH channels, i.e.

    .. math::  L \geq (N_{sph}+1)^2 .

    References
    ----------
    Zotter, F., Pomberger, H., & Noisternig, M. (2012). Energy-preserving
    ambisonic decoding. Acta Acustica United with Acustica, 98(1), 37–47.

    Examples
    --------
    .. plot::
        :context: close-figs

        ls_setup = spa.decoder.LoudspeakerSetup(ls_x, ls_y, ls_z)
        ls_setup.pop_triangles(normal_limit=85, aperture_limit=90,
                               opening_limit=150)

        spa.plots.decoder_performance(ls_setup, 'EPAD')

        spa.plots.decoder_performance(ls_setup, 'EPAD', N_sph=2,
                                      title='$N_{sph}=2$')

    """
    if N_sph is None:
        if hull.characteristic_order:
            N_sph = hull.characteristic_order
        else:
            N_sph = hull.get_characteristic_order()

    L = hull.npoints
    if (L < (N_sph + 1)**2):
        warn('EPAD needs more loudspeakers for this N_sph!'
             f' ({L} < {(N_sph+1)**2})')

    N_sph_in = int(np.sqrt(F_nm.shape[0]) - 1)
    assert (N_sph_in >= N_sph)  # for now

    # SVD of LS base
    ls_azi, ls_colat, ls_r = utils.cart2sph(*hull.points.T)
    Y_ls = sph.sh_matrix(N_sph, ls_azi, ls_colat, SH_type='real')
    U, S, VH = np.linalg.svd(Y_ls)
    # Set singular values to identity and truncate
    S_new = np.eye(L, (N_sph + 1)**2)
    D = U @ S_new @ VH
    # Scale to unity
    D *= np.sqrt(4 * np.pi / L)  # Amplitude to unity
    D *= np.sqrt(L / (N_sph + 1)**2)  # Energy to unity

    # loudspeaker output signals
    ls_sig = D @ F_nm[:(N_sph + 1)**2, :]
    return ls_sig