File size: 3,565 Bytes
f7fb447
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""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