示例#1
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
示例#2
0
# %% 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)
# ALLRAP2
gains_allrap2 = decoder.allrap2(src, ls_setup, N_sph=N_e)
# ALLRAD
input_F_nm = sph.sh_matrix(N_e, src_azi, src_colat, 'real').T  # SH dirac
out_allrad = decoder.allrad(input_F_nm, ls_setup, N_sph=N_e)
out_allrad2 = decoder.allrad2(input_F_nm, ls_setup, N_sph=N_e)

utils.test_diff(gains_allrap, out_allrad, msg="ALLRAD and ALLRAP:")
utils.test_diff(gains_allrap2, out_allrad2, msg="ALLRAD2 and ALLRAP2:")

# Nearest Loudspeaker
gains_nls = decoder.nearest_loudspeaker(src, ls_setup)

# %% test multiple sources
_grid, _weights = grids.load_Fliege_Maier_nodes(10)
G_vbap = decoder.vbap(_grid, ls_setup)
G_allrap = decoder.allrap(_grid, ls_setup)
G_allrap2 = decoder.allrap2(_grid, ls_setup)
G_vbip = decoder.vbip(_grid, ls_setup)
示例#3
0
文件: SH.py 项目: chris-hld/spaudiopy
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])
sig_t = np.c_[np.eye(4), np.eye(4)]  # second axis s(t)
sig_B = sph.soundfield_to_b(sig)
F_nm = sph.sht(sig, N, azi, colat, SH_type='real')
F_nm_t = sph.sht(sig_t, N, azi, colat, SH_type='real')

# %%
F_nm_c = sph.sht(sig, N, azi, colat, SH_type='complex')
F_nm_c_t = sph.sht(sig_t, N, azi, colat, SH_type='complex')

# %%
示例#4
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
示例#5
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
示例#6
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
示例#7
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