AIRA / aira /utils /formatter.py
nahue-passano
initial commit
f7fb447
"""3D format conversion for coordinates and Ambisonics"""
from functools import singledispatch
from typing import List, Tuple, Union
import numpy as np
@singledispatch
def convert_ambisonics_a_to_b(
front_left_up: np.ndarray,
front_right_down: np.ndarray,
back_right_up: np.ndarray,
back_left_down: np.ndarray,
) -> np.ndarray:
"""Converts Ambisonics A-format to B-format
Parameters
----------
front_left_up : np.ndarray
Front Left Up signal from A-format
front_right_down : np.ndarray
Front Right Down signal from A-format
back_right_up : np.ndarray
Back Right Up signal from A-format
back_left_down : np.ndarray
Back Left Down signal from A-format
Returns
-------
np.ndarray
B-format outputs (W, X, Y, Z)
"""
front = front_left_up + front_right_down
back = back_left_down + back_right_up
left = front_left_up + back_left_down
right = front_right_down + back_right_up
up = front_left_up + back_right_up # pylint: disable=invalid-name
down = front_right_down + back_left_down
w_channel = front + back
x_channel = front - back
y_channel = left - right
z_channel = up - down
return np.array([w_channel, x_channel, y_channel, z_channel])
@convert_ambisonics_a_to_b.register(list)
def _(aformat_channels: List[np.ndarray]) -> np.ndarray:
"""Converts Ambisonics A-format to B-format.
Parameters
----------
aformat_channels : List[np.ndarray]
A list containing the 4 channels of A-format Ambisonics in the following order:
1. Front Left Up
2. Front Right Down
3. Back Right Up
4. Back Left Down
Returns
-------
np.ndarray
B-format outputs (W, X, Y, Z)
"""
assert (
len(aformat_channels) == 4
), "Conversion from A-format to B-format requires 4 channels"
return convert_ambisonics_a_to_b.dispatch(np.ndarray)(
front_left_up=aformat_channels[0],
front_right_down=aformat_channels[1],
back_right_up=aformat_channels[2],
back_left_down=aformat_channels[3],
)
def spherical_to_cartesian(
radius: Union[float, np.ndarray],
azimuth: Union[float, np.ndarray],
elevation: Union[float, np.ndarray],
) -> Tuple[Union[float, np.ndarray]]:
"""Convert three 3D polar coordinates to Cartesian ones.
Parameters
radius: float | np.ndarray. The radii (or rho).
azimuth: float | np.ndarray. The azimuth (also called theta or alpha).
elevation: float | np.ndarray. The elevation (also called phi or polar).
Returns
(x, y, z): Tuple[float | np.ndarray]. The corresponding Cartesian coordinates.
"""
return (
radius * np.cos(np.deg2rad(azimuth)) * np.cos(np.deg2rad(elevation)),
radius * np.sin(np.deg2rad(azimuth)) * np.cos(np.deg2rad(elevation)),
radius * np.sin(np.deg2rad(elevation)),
)
def cartesian_to_spherical(intensity_windowed: np.ndarray):
"""_summary_
Parameters
----------
intensity_windowed : np.ndarray
_description_
Returns
-------
_type_
_description_
"""
# Convert to total intensity, azimuth and elevation
intensity = np.sqrt((intensity_windowed**2).sum(axis=0)).squeeze()
azimuth = np.rad2deg(
np.arctan2(intensity_windowed[1], intensity_windowed[0])
).squeeze()
elevation = np.rad2deg(np.arcsin(intensity_windowed[2] / intensity)).squeeze()
return intensity, azimuth, elevation