/*

Spectral statistics UGens for SuperCollider, by Dan Stowell.
Copyright (c) Dan Stowell 2006-2007.
Now part of SuperCollider 3, (c) James McCartney.

    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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

*/

#include "SC_PlugIn.h"
#include "SCComplex.h"
#include "FFT_UGens.h"

#include "ML.h"

//////////////////////////////////////////////////////////////////////////////////////////////////

/*
struct FFTAnalyser_Unit : Unit
{
    float outval;

    // Not always used: multipliers which convert from bin indices to freq vals, and vice versa.
    // See also the macros for deriving these.
    float m_bintofreq; // , m_freqtobin;
};

struct FFTAnalyser_OutOfPlace : FFTAnalyser_Unit
{
    int m_numbins;
    float *m_tempbuf;
};

struct SpecFlatness : FFTAnalyser_Unit
{
};

struct SpecPcile : FFTAnalyser_OutOfPlace
{
    bool m_interpolate;
};

struct SpecCentroid : FFTAnalyser_Unit
{
};
*/

//////////////////////////////////////////////////////////////////////////////////////////////////


// for operation on one buffer
// just like PV_GET_BUF except it outputs unit->outval rather than -1 when FFT not triggered
#define FFTAnalyser_GET_BUF                                                                                            \
    float fbufnum = ZIN0(0);                                                                                           \
    if (fbufnum < 0.f) {                                                                                               \
        ZOUT0(0) = unit->outval;                                                                                       \
        return;                                                                                                        \
    }                                                                                                                  \
    ZOUT0(0) = fbufnum;                                                                                                \
    uint32 ibufnum = (uint32)fbufnum;                                                                                  \
    World* world = unit->mWorld;                                                                                       \
    SndBuf* buf;                                                                                                       \
    if (!(ibufnum < world->mNumSndBufs)) {                                                                             \
        int localBufNum = ibufnum - world->mNumSndBufs;                                                                \
        Graph* parent = unit->mParent;                                                                                 \
        if (!(localBufNum > parent->localBufNum)) {                                                                    \
            buf = parent->mLocalSndBufs + localBufNum;                                                                 \
        } else {                                                                                                       \
            buf = world->mSndBufs;                                                                                     \
        }                                                                                                              \
    } else {                                                                                                           \
        buf = world->mSndBufs + ibufnum;                                                                               \
    }                                                                                                                  \
    LOCK_SNDBUF(buf);                                                                                                  \
    int numbins = (buf->samples - 2) >> 1;

// Copied from FFT_UGens.cpp
#define GET_BINTOFREQ                                                                                                  \
    if (unit->m_bintofreq == 0.f) {                                                                                    \
        unit->m_bintofreq = FULLRATE / buf->samples;                                                                   \
    }                                                                                                                  \
    float bintofreq = unit->m_bintofreq;
/*
#define GET_FREQTOBIN \
    if(unit->m_freqtobin==0.f){ \
        unit->m_freqtobin = buf->samples / FULLRATE; \
    } \
    float freqtobin = unit->m_freqtobin;
*/

//////////////////////////////////////////////////////////////////////////////////////////////////
/*
extern "C"
{
    void SpecFlatness_Ctor(SpecFlatness *unit);
    void SpecFlatness_next(SpecFlatness *unit, int inNumSamples);

    void SpecPcile_Ctor(SpecPcile *unit);
    void SpecPcile_next(SpecPcile *unit, int inNumSamples);
    void SpecPcile_Dtor(SpecPcile *unit);

    void SpecCentroid_Ctor(SpecCentroid *unit);
    void SpecCentroid_next(SpecCentroid *unit, int inNumSamples);

}
*/
/*
SCPolarBuf* ToPolarApx(SndBuf *buf)
{
    if (buf->coord == coord_Complex) {
        SCComplexBuf* p = (SCComplexBuf*)buf->data;
        int numbins = buf->samples - 2 >> 1;
        for (int i=0; i<numbins; ++i) {
            p->bin[i].ToPolarApxInPlace();
        }
        buf->coord = coord_Polar;
    }

    return (SCPolarBuf*)buf->data;
}

SCComplexBuf* ToComplexApx(SndBuf *buf)
{
    if (buf->coord == coord_Polar) {
        SCPolarBuf* p = (SCPolarBuf*)buf->data;
        int numbins = buf->samples - 2 >> 1;
        for (int i=0; i<numbins; ++i) {
            p->bin[i].ToComplexApxInPlace();
        }
        buf->coord = coord_Complex;
    }
    return (SCComplexBuf*)buf->data;
}

InterfaceTable *ft;

void init_SCComplex(InterfaceTable *inTable);
*/
//////////////////////////////////////////////////////////////////////////////////////////////////

void SpecFlatness_Ctor(SpecFlatness* unit) {
    SETCALC(SpecFlatness_next);
    ZOUT0(0) = unit->outval = 0.;
    unit->m_oneovern = 0.;
}

void SpecFlatness_next(SpecFlatness* unit, int inNumSamples) {
    FFTAnalyser_GET_BUF if (unit->m_oneovern == 0.) unit->m_oneovern = 1. / (numbins + 2);

    SCComplexBuf* p = ToComplexApx(buf);

    // Spectral Flatness Measure is geometric mean divided by arithmetic mean.
    //
    // In order to calculate geom mean without hitting the precision limit,
    //  we use the trick of converting to log, taking the average, then converting back from log.
    double geommean = std::log(sc_abs(p->dc)) + std::log(sc_abs(p->nyq));
    double mean = sc_abs(p->dc) + sc_abs(p->nyq);

    for (int i = 0; i < numbins; ++i) {
        float rabs = (p->bin[i].real);
        float iabs = (p->bin[i].imag);
        float amp = std::sqrt((rabs * rabs) + (iabs * iabs));
        if (amp != 0.f) { // zeroes lead to NaNs
            geommean += std::log(amp);
            mean += amp;
        }
    }

    double oneovern = unit->m_oneovern;
    geommean = exp(geommean * oneovern); // Average and then convert back to linear
    mean *= oneovern;

    // Store the val for output in future calls
    unit->outval = (mean == 0.f ? 0.8f : (geommean / mean));
    // Note: for silence the value is undefined.
    // Here, for silence we instead output an empirical value based on very quiet white noise.

    ZOUT0(0) = unit->outval;
}

////////////////////////////////////////////////////////////////////////////////////

void SpecPcile_Ctor(SpecPcile* unit) {
    SETCALC(SpecPcile_next);

    unit->m_interpolate = ZIN0(2) > 0.f;
    unit->m_binout = ZIN0(3) > 0.f;

    ZOUT0(0) = unit->outval = 0.;
    unit->m_tempbuf = nullptr;
}

void SpecPcile_next(SpecPcile* unit, int inNumSamples) {
    FFTAnalyser_GET_BUF

        // Used to be MAKE_TEMP_BUF but we can handle it more cleanly in this specific case:
        if (!unit->m_tempbuf) {
        unit->m_tempbuf = (float*)RTAlloc(unit->mWorld, numbins * sizeof(float));
        if (!unit->m_tempbuf) {
            ClearUnitOutputs(unit, inNumSamples);
            ClearUnitOnMemFailed;
        }
        unit->m_numbins = numbins;
        unit->m_halfnyq_over_numbinsp2 = ((float)FULLRATE) * 0.5f / (float)(numbins + 2);
    }
    else if (numbins != unit->m_numbins) return;

    // Percentile value as a fraction. eg: 0.5 == 50-percentile (median).
    float fraction = ZIN0(1);
    bool binout = unit->m_binout; // if true, output the bin number instead of freq
    bool interpolate = unit->m_interpolate;

    // The magnitudes in *p will be converted to cumulative sum values and stored in *q temporarily
    SCComplexBuf* p = ToComplexApx(buf);
    float* q = (float*)unit->m_tempbuf;

    float cumul = sc_abs(p->dc);

    for (int i = 0; i < numbins; ++i) {
        float real = p->bin[i].real;
        float imag = p->bin[i].imag;
        cumul += std::sqrt(real * real + imag * imag);

        // A convenient place to store the mag values...
        q[i] = cumul;
    }

    cumul += sc_abs(p->nyq);

    float target = cumul * fraction; // The target cumul value, stored somewhere in q

    float bestposition = 0; // May be linear-interpolated between bins, but not implemented yet
                            // NB If nothing beats the target (e.g. if fraction is -1) zero Hz is returned
    float binpos;
    for (int i = 0; i < numbins; ++i) {
        // Print("Testing %g, at position %i", q->bin[i].real, i);
        if (!(q[i] < target)) { // this is a ">=" comparison, done more efficiently as "!(<)"
            // output the bin number instead of freq
            if (binout) {
                if (interpolate && i != 0) {
                    bestposition = ((float)i) + (q[i] - target) / (q[i] - q[i - 1]);
                } else {
                    bestposition = i; // output bin directly
                }
            } else {
                if (interpolate && i != 0) {
                    binpos = ((float)i) + 1.f - (q[i] - target) / (q[i] - q[i - 1]);
                } else {
                    binpos = ((float)i) + 1.f;
                }
                bestposition = binpos * unit->m_halfnyq_over_numbinsp2;
            }
            // Print("Target %g beaten by %g (at position %i), equating to freq %g\n",
            //				target, p->bin[i].real, i, bestposition);
            break;
        }
    }

    // Store the val for output in future calls
    unit->outval = bestposition;

    ZOUT0(0) = unit->outval;
}


void SpecPcile_Dtor(SpecPcile* unit) {
    if (unit->m_tempbuf)
        RTFree(unit->mWorld, unit->m_tempbuf);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

void SpecCentroid_Ctor(SpecCentroid* unit) {
    SETCALC(SpecCentroid_next);
    ZOUT0(0) = unit->outval = 0.;

    unit->m_bintofreq = 0.f;
}

void SpecCentroid_next(SpecCentroid* unit, int inNumSamples) {
    FFTAnalyser_GET_BUF

        SCPolarBuf* p = ToPolarApx(buf);

    GET_BINTOFREQ


    double num = sc_abs(p->nyq) * (numbins + 1);
    double denom = sc_abs(p->nyq);

    for (int i = 0; i < numbins; ++i) {
        num += sc_abs(p->bin[i].mag) * (i + 1);
        denom += sc_abs(p->bin[i].mag);
    }

    ZOUT0(0) = unit->outval = denom == 0.0 ? 0.f : (float)(bintofreq * num / denom);
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*
void load(InterfaceTable *inTable)
{
    ft= inTable;

    //(*ft->fDefineUnit)("SpecFlatness", sizeof(FFTAnalyser_Unit), (UnitCtorFunc)&SpecFlatness_Ctor, 0, 0);
    //(*ft->fDefineUnit)("SpecPcile", sizeof(SpecPcile_Unit), (UnitCtorFunc)&SpecPcile_Ctor,
(UnitDtorFunc)&SpecPcile_Dtor, 0);

    DefineSimpleUnit(SpecFlatness);
    DefineDtorUnit(SpecPcile);
    DefineSimpleUnit(SpecCentroid);
}
*/
