File size: 2,131 Bytes
570b87b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""Decode HOA-7 coefficients to samples on the sphere."""

from __future__ import annotations

import numpy as np

from .basis import MAX_ORDER, sh_sn3d, sh_sn3d_batch, sphere_grid


def decode_directions(
    hoa: np.ndarray,
    azimuths: np.ndarray | float,
    elevations: np.ndarray | float,
    *,
    degrees: bool = True,
    max_order: int = MAX_ORDER,
) -> np.ndarray:
    """Sample field a · Y(Ω) at given directions.

    hoa: (C,) or (C, T)
    returns: (...) or (..., T)
    """
    a = np.asarray(hoa, dtype=np.float64)
    nch = (max_order + 1) ** 2
    # Allow shorter coefficient vectors (truncated order) or longer (ignore tail).
    if a.ndim == 1:
        aa = np.zeros(nch, dtype=np.float64)
        n = min(nch, a.shape[0])
        aa[:n] = a[:n]
        Y = sh_sn3d(azimuths, elevations, degrees=degrees, max_order=max_order)
        return np.einsum("...c,c->...", Y[..., :nch], aa)
    if a.ndim == 2:
        aa = np.zeros((nch, a.shape[1]), dtype=np.float64)
        n = min(nch, a.shape[0])
        aa[:n] = a[:n, :]
        Y = sh_sn3d(azimuths, elevations, degrees=degrees, max_order=max_order)
        return np.einsum("...c,ct->...t", Y[..., :nch], aa)
    raise ValueError("hoa must be (C,) or (C,T)")


def decode_grid(
    hoa: np.ndarray,
    n_azi: int = 72,
    n_el: int = 36,
    *,
    degrees: bool = True,
    max_order: int = MAX_ORDER,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Decode on a product sphere grid.

    Returns azi, el, samples with samples shape (n_azi, n_el) or (n_azi, n_el, T).
    """
    azi, el, _ = sphere_grid(n_azi, n_el, degrees=degrees)
    AA, EE = np.meshgrid(azi, el, indexing="ij")
    samp = decode_directions(hoa, AA, EE, degrees=degrees, max_order=max_order)
    return azi, el, samp


def beamform(
    hoa: np.ndarray,
    azimuth: float,
    elevation: float,
    *,
    degrees: bool = True,
    max_order: int = MAX_ORDER,
) -> float | np.ndarray:
    """Look / listen toward one direction: scalar (or time series) readout."""
    return decode_directions(
        hoa, azimuth, elevation, degrees=degrees, max_order=max_order
    )