HOA7 Spatial Field Decoder (hoa64 v0.5.0): 7th-order Ambisonics encode/decode, Wigner-D rotation, DOA analysis, vision fuse, diffusion conditioning
570b87b verified | """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 | |
| ) | |