/*
    SuperCollider real time audio synthesis system
    Copyright (c) 2002 James McCartney. All rights reserved.
    http://www.audiosynth.com

    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 "PyrSymbol.h"
#include "PyrKernel.h"
#include "PyrObject.h"
#include "PyrPrimitive.h"
#include "PyrSignal.h"
#include "PyrSignalPrim.h"
#include "PyrMessage.h"
#include "SC_Constants.h"
#include "SCBase.h"
#include "clz.h"
extern "C" {
#include "fftlib.h"
}
#include <stdlib.h>
#include <string.h>
#include <math.h>


int prSignalFill(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int err;
    float value;

    a = g->sp - 1;
    b = g->sp;

    err = slotFloatVal(b, &value);
    if (err != errNone)
        return err;
    signal_fill(slotRawObject(a), value);
    return errNone;
}

int prSignalScale(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int err;
    float value;

    a = g->sp - 1;
    b = g->sp;

    err = slotFloatVal(b, &value);
    if (err != errNone)
        return err;
    signal_scale(slotRawObject(a), value);
    return errNone;
}

int prSignalOffset(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int err;
    float value;

    a = g->sp - 1;
    b = g->sp;

    err = slotFloatVal(b, &value);
    if (err != errNone)
        return err;
    signal_offset(slotRawObject(a), value);
    return errNone;
}

int prSignalString(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;
    PyrString* string;
    PyrObject* signal;
    float* x;
    char str[128];

    a = g->sp;
    slotString(a, str);

    signal = slotRawObject(a);
    if (signal->size) {
        x = (float*)(signal->slots);
        sprintf(str, "%s[%g .. %g]", slotRawSymbol(&signal->classptr->name)->name, x[0], x[signal->size - 1]);
    } else {
        sprintf(str, "%s[none]", slotRawSymbol(&signal->classptr->name)->name);
    }
    string = newPyrString(g->gc, str, 0, true);
    SetObject(a, string);
    return errNone;
}

int prSignalPeak(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;

    a = g->sp;

    SetFloat(a, signal_findpeak(slotRawObject(a)));
    return errNone;
}

int prSignalNormalize(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err, start, end;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    err = slotIntVal(b, &start);
    if (err) {
        if (IsNil(c))
            start = 0;
        else
            return err;
    }

    err = slotIntVal(c, &end);
    if (err) {
        if (IsNil(c))
            end = slotRawObject(a)->size;
        else
            return err;
    }

    signal_normalize_range(slotRawObject(a), start, end);
    return errNone;
}

int prSignalNormalizeTransferFn(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;

    a = g->sp;

    signal_normalize_transfer_fn(slotRawObject(a));
    return errNone;
}

int prSignalIntegral(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;

    a = g->sp;

    SetFloat(a, signal_integral(slotRawObject(a)));
    return errNone;
}

int prSignalInvert(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err, start, end;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    err = slotIntVal(b, &start);
    if (err) {
        if (IsNil(c))
            start = 0;
        else
            return err;
    }

    err = slotIntVal(c, &end);
    if (err) {
        if (IsNil(c))
            end = slotRawObject(a)->size;
        else
            return err;
    }

    signal_invert_range(slotRawObject(a), start, end);
    return errNone;
}

int prSignalReverse(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err, start, end;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    err = slotIntVal(b, &start);
    if (err) {
        if (IsNil(b))
            start = 0;
        else
            return err;
    }

    err = slotIntVal(c, &end);
    if (err) {
        if (IsNil(c))
            end = slotRawObject(a)->size;
        else
            return err;
    }

    signal_reverse_range(slotRawObject(a), start, end);
    return errNone;
}

int prSignalRotate(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b;
    int err, rot;

    a = g->sp - 1;
    b = g->sp;

    err = slotIntVal(b, &rot);
    if (err)
        return err;

    SetRaw(a, signal_rotate(g, slotRawObject(a), rot));
    return errNone;
}

int prSignalOverDub(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err;
    int index;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    if (NotObj(b) || !isKindOf(slotRawObject(b), class_signal))
        return errWrongType;
    err = slotIntVal(c, &index);
    if (err)
        return errWrongType;

    signal_overdub(g, slotRawObject(a), slotRawObject(b), index);
    return errNone;
}

int prSignalOverWrite(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err;
    int index;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    if (NotObj(b) || !isKindOf(slotRawObject(b), class_signal))
        return errWrongType;
    err = slotIntVal(c, &index);
    if (err)
        return errWrongType;

    signal_overwrite(g, slotRawObject(a), slotRawObject(b), index);
    return errNone;
}

int prSignalFade(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *d, *e;
    int err;
    int start, end;
    float lvl0, lvl1;

    a = g->sp - 4;
    b = g->sp - 3;
    c = g->sp - 2;
    d = g->sp - 1;
    e = g->sp;

    err = slotIntVal(b, &start);
    if (err) {
        if (IsNil(b))
            start = 0;
        else
            return err;
    }

    err = slotIntVal(c, &end);
    if (err) {
        if (IsNil(c))
            end = slotRawObject(a)->size;
        else
            return err;
    }

    err = slotFloatVal(d, &lvl0);
    if (err)
        return err;

    err = slotFloatVal(e, &lvl1);
    if (err)
        return err;

    signal_fade_range(slotRawObject(a), start, end, lvl0, lvl1);
    return errNone;
}

int prSignalAddHarmonic(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c, *d;
    int err;
    float harmonic, amp;
    double phase, step;
    PyrObject* signal;
    float* out;

    a = g->sp - 3;
    b = g->sp - 2;
    c = g->sp - 1;
    d = g->sp;

    err = slotFloatVal(b, &harmonic);
    if (err)
        return errWrongType;
    err = slotFloatVal(c, &amp);
    if (err)
        return errWrongType;
    err = slotDoubleVal(d, &phase);
    if (err)
        return errWrongType;

    signal = slotRawObject(a);
    out = (float*)(signal->slots) - 1;
    step = twopi * harmonic / signal->size;
    UNROLL_CODE(signal->size, out, *++out += sin(phase) * amp; phase += step;);

    return errNone;
}

int prSignalAddChebyshev(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;
    int err;
    float harmonic, amp;
    double x, step;
    PyrObject* signal;
    float* out;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    err = slotFloatVal(b, &harmonic);
    if (err)
        return errWrongType;
    err = slotFloatVal(c, &amp);
    if (err)
        return errWrongType;

    signal = slotRawObject(a);
    out = (float*)(signal->slots) - 1;
    x = -1.0;
    step = 2.0 / (signal->size - 1);
    UNROLL_CODE(signal->size, out, *++out += cos(harmonic * acos(sc_min(x, 1.0))) * amp; x += step;);

    return errNone;
}

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

void signalAsWavetable(float* signal, float* wavetable, int size) {
    int i, imax;
    float *in, *out;
    float val1, val2;

    in = signal;
    out = wavetable - 1;
    imax = size - 1;
    for (i = 0; i < imax; ++i) {
        val1 = in[i];
        val2 = in[i + 1];
        *++out = 2.f * val1 - val2;
        *++out = val2 - val1;
    }
    val1 = in[imax];
    val2 = in[0];
    *++out = 2.f * val1 - val2;
    *++out = val2 - val1;
}

void wavetableAsSignal(float* wavetable, float* signal, int size) {
    float *in, *out;
    in = wavetable;
    out = signal - 1;
    for (int i = 0; i < size; ++i) {
        *++out = in[0] + in[1];
        in += 2;
    }
}


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

int prSignalAsWavetable(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;
    int size;
    PyrObject *signal, *wavetable;

    a = g->sp;

    signal = slotRawObject(a);

    size = signal->size;
    if ((size & (size - 1)) != 0) {
        error("Signal size not a power of two.\n");
        return errFailed;
    }
    wavetable = newPyrSignal(g, signal->size * 2);
    wavetable->classptr = class_wavetable;
    signalAsWavetable((float*)signal->slots, (float*)wavetable->slots, signal->size);

    SetObject(a, wavetable);
    return errNone;
}


int prWavetableAsSignal(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot* a;
    PyrObject *signal, *wavetable;

    a = g->sp;

    wavetable = slotRawObject(a);
    signal = newPyrSignal(g, wavetable->size / 2);
    wavetableAsSignal((float*)wavetable->slots, (float*)signal->slots, signal->size);
    SetObject(a, signal);
    return errNone;
}
// class_signal

#if 1
int prSignal_FFT(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;

    int i, M, fftsize, fftbufsize, asize;
    float *inreal, *inimag, *fftbuf, *costable, *outreal, *outimag;
    PyrObject *fftoutobj, *complexobj, *realobj, *imagobj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    asize = slotRawObject(a)->size;
    if (NotNil(b) && !(isKindOfSlot(b, class_signal) && slotRawObject(b)->size == asize)) {
        error("Signal::fft imaginary part wrong type or length.\n");
        return errFailed;
    }

    M = LOG2CEIL(asize);
    fftsize = 1LL << M;

    if (!(isKindOfSlot(c, class_floatarray))) {
        error("Signal::fft must be provided a table containing 1/4 cycle of a cosine.\n");
        return errFailed;
    }
    if (slotRawObject(c)->size != fftsize / 4 + 1) {
        error("Signal::fft cosine table wrong size (%d), expected %d.\n", slotRawObject(c)->size, fftsize / 4 + 1);
        return errFailed;
    }
    costable = (float*)slotRawObject(c)->slots;

    fftbufsize = fftsize * 2;
    fftoutobj = newPyrSignal(g, fftbufsize);
    fftoutobj->size = fftbufsize;
    ++g->sp;
    SetObject(g->sp, fftoutobj);

    complexobj = instantiateObject(g->gc, s_complex->u.classobj, 0, false, true);
    ++g->sp;
    SetObject(g->sp, complexobj);

    realobj = newPyrSignal(g, fftbufsize);
    SetObject(complexobj->slots + 0, realobj);
    g->gc->GCWriteNew(complexobj, realobj);

    imagobj = newPyrSignal(g, fftbufsize);
    SetObject(complexobj->slots + 1, imagobj);
    g->gc->GCWriteNew(complexobj, imagobj);

    inreal = (float*)slotRawObject(a)->slots - 1;
    if (IsNil(b)) {
        fftbuf = (float*)fftoutobj->slots - 1;
        for (i = 0; i < asize; ++i) {
            *++fftbuf = *++inreal;
        }
        for (i = asize; i < fftsize; ++i) {
            *++fftbuf = 0.f;
        }

        fftbuf = (float*)fftoutobj->slots;
        rffts(fftbuf, M, 1, costable);
    } else {
        inimag = (float*)slotRawObject(b)->slots - 1;

        fftbuf = (float*)fftoutobj->slots - 1;
        for (i = 0; i < asize; ++i) {
            *++fftbuf = *++inreal;
            *++fftbuf = *++inimag;
        }
        for (i = asize; i < fftsize; ++i) {
            *++fftbuf = 0.f;
            *++fftbuf = 0.f;
        }

        fftbuf = (float*)fftoutobj->slots;
        ffts(fftbuf, M, 1, costable);
    }
    outreal = (float*)realobj->slots - 1;
    outimag = (float*)imagobj->slots - 1;
    fftbuf = (float*)fftoutobj->slots - 1;
    for (i = 0; i < fftsize; ++i) {
        *++outreal = *++fftbuf;
        *++outimag = *++fftbuf;
    }
    realobj->size = fftsize;
    imagobj->size = fftsize;

    g->sp -= 2;
    SetRaw(a, complexobj);

    return errNone;
}

int prSignal_IFFT(struct VMGlobals* g, int numArgsPushed) {
    PyrSlot *a, *b, *c;

    int i, M, fftsize, fftbufsize, asize;
    float *inreal, *inimag, *fftbuf, *costable, *outreal, *outimag;
    PyrObject *fftoutobj, *complexobj, *realobj, *imagobj;

    a = g->sp - 2;
    b = g->sp - 1;
    c = g->sp;

    asize = slotRawObject(a)->size;
    if (!(isKindOfSlot(b, class_signal) && slotRawObject(b)->size == asize)) {
        error("Signal::ifft imaginary part wrong type or length.\n");
        return errFailed;
    }

    M = LOG2CEIL(asize);
    fftsize = 1LL << M;

    if (!(isKindOfSlot(c, class_floatarray))) {
        error("Signal::ifft must be provided a table containing 1/4 cycle of a cosine.\n");
        return errFailed;
    }
    if (slotRawObject(c)->size != fftsize / 4 + 1) {
        error("Signal::ifft cosine table wrong size (%d), expected %d.\n", slotRawObject(c)->size, fftsize / 4 + 1);
        return errFailed;
    }
    costable = (float*)slotRawObject(c)->slots;

    fftbufsize = fftsize * 2;
    fftoutobj = newPyrSignal(g, fftbufsize);
    fftoutobj->size = fftbufsize;
    ++g->sp;
    SetObject(g->sp, fftoutobj);

    complexobj = instantiateObject(g->gc, s_complex->u.classobj, 0, false, true);
    ++g->sp;
    SetObject(g->sp, complexobj);

    realobj = newPyrSignal(g, fftbufsize);
    SetObject(complexobj->slots + 0, realobj);
    g->gc->GCWriteNew(complexobj, realobj);

    imagobj = newPyrSignal(g, fftbufsize);
    SetObject(complexobj->slots + 1, imagobj);
    g->gc->GCWriteNew(complexobj, imagobj);

    inreal = (float*)slotRawObject(a)->slots - 1;
    inimag = (float*)slotRawObject(b)->slots - 1;

    fftbuf = (float*)fftoutobj->slots - 1;
    for (i = 0; i < asize; ++i) {
        *++fftbuf = *++inreal;
        *++fftbuf = *++inimag;
    }
    for (i = asize; i < fftsize; ++i) {
        *++fftbuf = 0.f;
        *++fftbuf = 0.f;
    }

    fftbuf = (float*)fftoutobj->slots;
    iffts(fftbuf, M, 1, costable);

    outreal = (float*)realobj->slots - 1;
    outimag = (float*)imagobj->slots - 1;
    fftbuf = (float*)fftoutobj->slots - 1;
    for (i = 0; i < fftsize; ++i) {
        *++outreal = *++fftbuf;
        *++outimag = *++fftbuf;
    }
    realobj->size = fftsize;
    imagobj->size = fftsize;

    g->sp -= 2;
    SetRaw(a, complexobj);

    return errNone;
}
#endif

void initSignalPrimitives() {
    int base, index;

    base = nextPrimitiveIndex();
    index = 0;
    definePrimitive(base, index++, "_SignalPeak", prSignalPeak, 1, 0);
    definePrimitive(base, index++, "_SignalNormalize", prSignalNormalize, 3, 0);
    definePrimitive(base, index++, "_SignalNormalizeTransferFn", prSignalNormalizeTransferFn, 1, 0);
    definePrimitive(base, index++, "_SignalIntegral", prSignalIntegral, 1, 0);
    definePrimitive(base, index++, "_SignalReverse", prSignalReverse, 3, 0);
    definePrimitive(base, index++, "_SignalInvert", prSignalInvert, 3, 0);
    definePrimitive(base, index++, "_SignalRotate", prSignalRotate, 2, 0);

    definePrimitive(base, index++, "_SignalFill", prSignalFill, 2, 0);
    definePrimitive(base, index++, "_SignalScale", prSignalScale, 2, 0);
    definePrimitive(base, index++, "_SignalOffset", prSignalOffset, 2, 0);
    definePrimitive(base, index++, "_SignalOverDub", prSignalOverDub, 3, 0);
    definePrimitive(base, index++, "_SignalOverWrite", prSignalOverWrite, 3, 0);
    definePrimitive(base, index++, "_SignalFade", prSignalFade, 5, 0);
    definePrimitive(base, index++, "_SignalAddHarmonic", prSignalAddHarmonic, 4, 0);
    definePrimitive(base, index++, "_SignalAddChebyshev", prSignalAddChebyshev, 3, 0);
    definePrimitive(base, index++, "_SignalString", prSignalString, 1, 0);
    definePrimitive(base, index++, "_SignalAsWavetable", prSignalAsWavetable, 1, 0);
    definePrimitive(base, index++, "_WavetableAsSignal", prWavetableAsSignal, 1, 0);

    definePrimitive(base, index++, "_Signal_FFT", prSignal_FFT, 3, 0);
    definePrimitive(base, index++, "_Signal_IFFT", prSignal_IFFT, 3, 0);
}
