// ----------------------------------------------------------------------------
//
//  Copyright (C) 2013-2021 Fons Adriaensen <fons@linuxaudio.org>
//    
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <string.h>
#include <math.h>


#define MAXD 8


//  Evaluate associated Legendre polynomials up to given degree.
//  The lower triangle of A contains the result.
//
static void alegendre (int degree, double A [MAXD+1][MAXD+1], double z)
{
    int    k, m, n;
    double t;  

    memset (A, 0, (MAXD+1) * (MAXD+1) * sizeof (double));
    A [0][0] = 1.0;
    if (degree == 0) return;
    t = sqrt (1.0 - z * z);
    A [1][0] = z;
    A [1][1] = t;
    for (m = 2, k = 3; m <= degree; m++, k += 2)
    {
        A [m][0] = (k * z * A [m - 1][0] - (m - 1) * A [m - 2][0]) / m;
        for (n = 1; n <= m; n++)
        {
            A [m][n] =  k * t * A [m - 1][n - 1] + A [m - 2][n];
        }
    }
}


static void realspharm_csz (int degree, double c, double s, double z,
                            float *H, bool semi)
{
    int     k, m, n;
    double  a, b, g, t;
    double  A [MAXD+1][MAXD+1];
    double  C [MAXD+1];
    double  S [MAXD+1];
        
    H [0] = 1.0f;
    if (degree < 1) return;
    C [0] = 1.0;
    S [0] = 0.0;
    C [1] = c;
    S [1] = s;
    alegendre (degree, A, z);
    k = 0;
    for (m = 1; m <= degree; m++)
    {
        k += 2 * m;
        g = semi ? 1.0 : 2 * m + 1.0;
        H [k] = A [m][0] * sqrt (g);
        g *= 2;
        a = b = m;
        for (n = 1; n <= m; n++)
        {
            a += 1;
            g /= a * b;
            b -= 1;
            t = A [m][n] * sqrt (g);
            H [k + n] = t * C [n];
            H [k - n] = t * S [n];
        }
        if (m < degree)
        {
            C [m + 1] = c * C [m] - s * S [m];
            S [m + 1] = c * S [m] + s * C [m];
        }
    }
}
                
// Evaluate normalised or semi-normalised real spherical
// harmonics up to the given degree. Azimuth and elevation
// are in rads. The result in H is in ACN order.
//
void realspharm (int degree, double azim, double elev, float *H, bool semi)
{
    if (degree > MAXD) degree = MAXD;
    if (cos (elev) < 0) azim += M_PI;
    realspharm_csz (degree, cos (azim), sin (azim), sin (elev), H, semi);
}

