/*
    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
*/

/*
    compound formulas :
    amclip	out = B<=0 ? 0 : A*B;		// two quadrant amplitude modulation
    ring1	out = A*(B+1) = A*B + A;	// amplitude modulation of a by b.
    ring2	out = A*B + A + B;			// ring modulation plus both original signals
    ring3	out = A*A*B;				// ring modulation variant
    ring4	out = A*A*B - A*B*B;		// ring modulation variant
    difsqr	out = A*A - B*B;			// difference of squares
    sumsqr	out = A*A + B*B;			// sum of squares
    sqrdif	out = (A - B)^2				// square of the difference = a^2 + b^2 - 2ab
    sqrsum	out = (A + B)^2				// square of the sum        = a^2 + b^2 + 2ab
*/

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "PyrSignal.h"
#include "PyrKernel.h"
#include "SC_InlineUnaryOp.h"
#include "SC_InlineBinaryOp.h"
#include "SCBase.h"
#include "InitAlloc.h"

PyrObject* newPyrSignal(VMGlobals* g, long size) {
    PyrObject* signal;
    long numbytes = size * sizeof(float);
    signal = (PyrObject*)g->gc->New(numbytes, 0, obj_float, true);
    if (signal) {
        signal->classptr = class_signal;
        signal->size = size;
    }
    // note: signal is filled with garbage
    return signal;
}

/*PyrObject* newPyrSignalFrom(VMGlobals *g, PyrObject* inSignal, long size)
{
    PyrObject *signal;
    double *pslot, *qslot, *endptr;
    long set, m, mmax;
    long numbytes = size * sizeof(float);
    signal = (PyrObject*)g->gc->New(numbytes, 0, obj_float, true);
    signal->classptr = inSignal->classptr;
    signal->size = size;
    return signal;
}
*/

PyrObject* signal_fill(PyrObject* outSignal, float inValue) {
    float* out = (float*)(outSignal->slots) - 1;
    UNROLL_CODE(outSignal->size, out, *++out = inValue;);
    return outSignal;
}

PyrObject* signal_scale(PyrObject* outSignal, float inValue) {
    if (inValue != 1.f) {
        float* out = (float*)(outSignal->slots) - 1;
        UNROLL_CODE(outSignal->size, out, *++out *= inValue;)
    }
    return outSignal;
}

PyrObject* signal_offset(PyrObject* outSignal, float inValue) {
    if (inValue != 0.f) {
        float* out = (float*)(outSignal->slots) - 1;
        UNROLL_CODE(outSignal->size, out, *++out += inValue;)
    }
    return outSignal;
}

PyrObject* signal_add_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP1(+); }

PyrObject* signal_mul_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP1(*); }

PyrObject* signal_sub_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP2(*++c = *++a - *++b;); }

PyrObject* signal_ring1_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP2(++a; *++c = *a * *++b + *a;); }

PyrObject* signal_ring2_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a * *b + *a + *b;);
}

PyrObject* signal_ring3_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP2(++a; *++c = *a * *a * *++b;); }

PyrObject* signal_ring4_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a * *a * *b - *a * *b * *b;);
}

PyrObject* signal_thresh_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a < *b ? 0.f : *a;);
}

PyrObject* signal_amclip_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *b <= 0.f ? 0.f : *a * *b;);
}

PyrObject* signal_div_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP2(*++c = *++a / *++b;); }

PyrObject* signal_difsqr_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *c = *a * *a - *b * *b;);
}

PyrObject* signal_sumsqr_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *c = *a * *a + *b * *b;);
}

PyrObject* signal_sqrsum_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    float z;
    BINOP_LOOP2(z = *++a + *++b; *++c = z * z;);
}

PyrObject* signal_sqrdif_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    float z;
    BINOP_LOOP2(z = *++a - *++b; *++c = z * z;);
}

PyrObject* signal_absdif_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) { BINOP_LOOP2(*++c = fabs(*++a - *++b);); }

PyrObject* signal_add_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    if (inb == 0.f) {
        float* a = (float*)(ina->slots);
        memcpy((float*)(outc->slots), a, ina->size * sizeof(float));
    } else {
        float* a = (float*)(ina->slots) - 1;
        float* c = (float*)(outc->slots) - 1;
        UNROLL_CODE(outc->size, c, *++c = *++a + inb;)
    }
    return outc;
}

PyrObject* signal_sub_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    if (inb == 0.f) {
        float* a = (float*)(ina->slots);
        memcpy((float*)(outc->slots), a, ina->size * sizeof(float));
    } else {
        float* a = (float*)(ina->slots) - 1;
        float* c = (float*)(outc->slots) - 1;
        UNROLL_CODE(outc->size, c, *++c = *++a - inb;)
    }
    return outc;
}

PyrObject* signal_mul_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    if (inb == 1.f) {
        float* a = (float*)(ina->slots);
        memcpy((float*)(outc->slots), a, ina->size * sizeof(float));
    } else {
        float* a = (float*)(ina->slots) - 1;
        float* c = (float*)(outc->slots) - 1;
        UNROLL_CODE(outc->size, c, *++c = *++a * inb;)
    }
    return outc;
}

PyrObject* signal_ring1_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * inb + *a;)
    return outc;
}

PyrObject* signal_ring2_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * inb + *a + inb;)
    return outc;
}

PyrObject* signal_ring3_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * *a * inb;)
    return outc;
}

PyrObject* signal_ring4_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    float inb2 = inb * inb;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * *a * inb + *a * inb2;)
    return outc;
}


PyrObject* signal_thresh_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a < inb ? 0.f : *a;)
    return outc;
}

PyrObject* signal_amclip_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* res;
    PyrObject* outc = newPyrSignal(g, ina->size);
    if (inb <= 0.f)
        res = signal_fill(outc, 0.f);
    else
        res = signal_scale(outc, inb);
    return res;
}

PyrObject* signal_div_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    if (inb == 1.f) {
        float* a = (float*)(ina->slots);
        memcpy((float*)(outc->slots), a, ina->size * sizeof(float));
    } else {
        float* a = (float*)(ina->slots) - 1;
        float* c = (float*)(outc->slots) - 1;
        inb = 1.f / inb;
        UNROLL_CODE(outc->size, c, *++c = *++a * inb;)
    }
    return outc;
}

PyrObject* signal_difsqr_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    inb = inb * inb;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * *a - inb;)
    return outc;
}

PyrObject* signal_sumsqr_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    inb = inb * inb;
    UNROLL_CODE(outc->size, c, ++a; *++c = *a * *a + inb;)
    return outc;
}

PyrObject* signal_sqrsum_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    float temp;
    UNROLL_CODE(outc->size, c, ++a; temp = *a + inb; *++c = temp * temp;)
    return outc;
}

PyrObject* signal_sqrdif_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    float temp;
    UNROLL_CODE(outc->size, c, ++a; temp = *a - inb; *++c = temp * temp;)
    return outc;
}

PyrObject* signal_absdif_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++a; *++c = fabs(*a - inb);)
    return outc;
}

PyrObject* signal_ring1_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, *++c = ina * *++b + ina;)
    return outc;
}

PyrObject* signal_ring2_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++b; *++c = ina * *b + ina + *b;)
    return outc;
}

PyrObject* signal_ring3_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    float ina2 = ina * ina;
    UNROLL_CODE(outc->size, c, *++c = ina2 * *++b;)
    return outc;
}

PyrObject* signal_ring4_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    float ina2 = ina * ina;
    UNROLL_CODE(outc->size, c, ++b; *++c = ina2 * *b - ina * *b * *b;)
    return outc;
}

PyrObject* signal_thresh_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = ina < *b ? 0.f : ina;)
    return outc;
}

PyrObject* signal_amclip_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = *b <= 0.f ? 0.f : ina * *b;)
    return outc;
}

PyrObject* signal_sub_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, *++c = ina - *++b;)
    return outc;
}

PyrObject* signal_div_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, *++c = ina / *++b;)
    return outc;
}

PyrObject* signal_difsqr_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    ina = ina * ina;
    UNROLL_CODE(outc->size, c, ++b; *++c = ina - *b * *b;)
    return outc;
}

PyrObject* signal_sumsqr_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    ina = ina * ina;
    UNROLL_CODE(outc->size, c, ++b; *++c = ina + *b * *b;)
    return outc;
}

PyrObject* signal_sqrsum_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float temp;
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++b; temp = ina + *b; *++c = temp * temp;)
    return outc;
}

PyrObject* signal_sqrdif_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float temp;
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++b; temp = ina - *b; *++c = temp * temp;)
    return outc;
}

PyrObject* signal_absdif_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL_CODE(outc->size, c, ++b; *++c = fabs(ina - *b);)
    return outc;
}

PyrObject* signal_min_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a < *b ? *a : *b;);
}

PyrObject* signal_max_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a > *b ? *a : *b;);
}

PyrObject* signal_scaleneg_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a < 0.f ? *a * *b : *a;);
}

PyrObject* signal_clip2_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a > *b ? *b : (*a < -*b ? -*b : *a););
}

PyrObject* signal_fold2_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = sc_fold(*a, -*b, *b););
}

PyrObject* signal_wrap2_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = sc_wrap(*a, -*b, *b););
}

PyrObject* signal_excess_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    BINOP_LOOP2(++a; ++b; *++c = *a > *b ? *a - *b : (*a < -*b ? *a + *b : 0.f););
}

bool signal_equal_xx(VMGlobals* g, PyrObject* ina, PyrObject* inb) {
    if (ina->size != inb->size)
        return false;
    if (slotRawSymbol(&ina->slots[kSignalRate]) != slotRawSymbol(&inb->slots[kSignalRate]))
        return false;
    float* a = (float*)(ina->slots);
    float* b = (float*)(inb->slots);
    for (int i = 0; i < ina->size; i++) {
        if (a[i] != b[i]) {
            return false;
        }
    }
    return true;
}

PyrObject* signal_min_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = *a < inb ? *a : inb;)
    return outc;
}

PyrObject* signal_max_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = *a > inb ? *a : inb;)
    return outc;
}

PyrObject* signal_scaleneg_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = *a < 0.f ? *a * inb : *a;)
    return outc;
}

PyrObject* signal_clip2_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = *a > inb ? inb : (*a < -inb ? -inb : *a);)
    return outc;
}

PyrObject* signal_fold2_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = sc_fold(*a, -inb, inb);)
    return outc;
}

PyrObject* signal_wrap2_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = sc_wrap(*a, -inb, inb);)
    return outc;
}

PyrObject* signal_excess_xf(VMGlobals* g, PyrObject* ina, float inb) {
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a = (float*)(ina->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++a; *++c = *a > inb ? *a - inb : (*a < -inb ? *a + inb : 0.f);)
    return outc;
}

PyrObject* signal_scaleneg_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    if (ina < 0.f) {
        UNROLL1_CODE(outc->size, c, *++c = ina * *++b;);
    } else {
        UNROLL1_CODE(outc->size, c, *++c = ina;);
    }
    return outc;
}

PyrObject* signal_clip2_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = ina > *b ? *b : (ina < -*b ? -*b : ina);)
    return outc;
}

PyrObject* signal_fold2_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = sc_fold(ina, -*b, *b);)
    return outc;
}

PyrObject* signal_wrap2_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = sc_wrap(ina, -*b, *b);)
    return outc;
}

PyrObject* signal_excess_fx(VMGlobals* g, float ina, PyrObject* inb) {
    PyrObject* outc = newPyrSignal(g, inb->size);
    float* b = (float*)(inb->slots) - 1;
    float* c = (float*)(outc->slots) - 1;
    UNROLL1_CODE(outc->size, c, ++b; *++c = ina > *b ? ina - *b : (ina < -*b ? ina + *b : 0.f);)
    return outc;
}

PyrObject* signal_invert(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL_CODE(inPyrSignal->size, out, *++out = -*++in;)
    return outc;
}

PyrObject* signal_recip(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL_CODE(inPyrSignal->size, out, *++out = 1.f / *++in;)
    return outc;
}

PyrObject* signal_squared(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL_CODE(inPyrSignal->size, out, ++in; *++out = *in * *in;)
    return outc;
}

PyrObject* signal_cubed(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL_CODE(inPyrSignal->size, out, ++in; *++out = *in * *in * *in;)
    return outc;
}

// PowerPC has a fast fabs instruction
PyrObject* signal_abs(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL_CODE(inPyrSignal->size, out, *++out = fabs(*++in);)
    return outc;
}

float signal_findpeak(PyrObject* inPyrSignal) {
    float* out = (float*)(inPyrSignal->slots) - 1;
    float peak = 0.f;
    float z;
    UNROLL1_CODE(inPyrSignal->size, out, ++out; z = fabs(*out); peak = z > peak ? z : peak;)
    return peak;
}

PyrObject* signal_normalize_transfer_fn(PyrObject* inPyrSignal) {
    float *out, scale, offset, x, maxval;
    long length, halflength;

    maxval = 0.0;
    out = (float*)(inPyrSignal->slots) - 1;
    length = inPyrSignal->size;
    halflength = length >> 1;
    offset = (out[halflength - 1] + out[halflength]) * 0.5;

    UNROLL1_CODE(inPyrSignal->size, out, ++out; x = *out - offset; x = (x < 0.) ? -x : x; if (x > maxval) maxval = x;);
    if (maxval) {
        out = (float*)(inPyrSignal->slots) - 1;
        scale = 1.0 / maxval;
        UNROLL1_CODE(inPyrSignal->size, out, ++out; *out = (*out - offset) * scale;);
    }
    return inPyrSignal;
}


float signal_integral(PyrObject* inPyrSignal) {
    float* out = (float*)(inPyrSignal->slots) - 1;
    float sum = 0.f;
    UNROLL1_CODE(inPyrSignal->size, out, sum += *++out;)
    return sum;
}

PyrObject* signal_sign(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, ++in; *++out = *in < 0.f ? -1.f : (*in > 0.f ? 1.f : 0.f);)
    return outc;
}

PyrObject* signal_negative(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = *++in < 0.f ? 1.f : 0.f;)
    return outc;
}

PyrObject* signal_positive(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = *++in >= 0.f ? 1.f : 0.f;)
    return outc;
}

PyrObject* signal_strictly_positive(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = *++in > 0.f ? 1.f : 0.f;)
    return outc;
}

PyrObject* signal_nyqring(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    switch (inPyrSignal->size & 3) {
        while (out < endptr) {
            *++out = -*++in;
        case 3:
            *++out = *++in;
        case 2:
            *++out = -*++in;
        case 1:
            *++out = *++in;
        case 0:;
        }
    }
    return outc;
}

PyrObject* signal_clip_f(VMGlobals* g, PyrObject* inPyrSignal, float lo, float hi) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    while (out < endptr) {
        ++in;
        *++out = *in < lo ? lo : (*in > hi ? hi : *in);
    }
    return outc;
}

PyrObject* signal_clip_x(VMGlobals* g, PyrObject* ina, PyrObject* inb, PyrObject* inc) {
    PyrObject* outd;
    float *a, *b, *c, *d, *endptr;
    long minsize = sc_min(ina->size, inb->size);
    minsize = sc_min(minsize, inc->size);
    outd = newPyrSignal(g, minsize);
    a = (float*)(ina->slots) - 1;
    b = (float*)(inb->slots) - 1;
    c = (float*)(inc->slots) - 1;
    d = (float*)(outd->slots) - 1;
    endptr = a + minsize;
    UNROLL1_CODE(outd->size, d, ++a; ++b; ++c; *++d = *a < *b ? *b : (*a > *c ? *c : *a););
    return outd;
}


/// WRAP AND FOLD ARE STILL INCORRECT !

PyrObject* signal_wrap_f(VMGlobals* g, PyrObject* inPyrSignal, float lo, float hi) {
    float* out = (float*)(inPyrSignal->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    while (out < endptr) {
        ++out;
        *out = sc_wrap(*out, lo, hi);
    }
    return inPyrSignal;
}

PyrObject* signal_wrap_x(VMGlobals* g, PyrObject* ina, PyrObject* inb, PyrObject* inc) {
    PyrObject* outd;
    float *a, *b, *c, *d, *endptr;
    long minsize = sc_min(ina->size, inb->size);
    minsize = sc_min(minsize, inc->size);
    outd = newPyrSignal(g, minsize);
    a = (float*)(ina->slots) - 1;
    b = (float*)(inb->slots) - 1;
    c = (float*)(inc->slots) - 1;
    d = (float*)(outd->slots) - 1;
    endptr = d + outd->size;
    while (d < endptr) {
        a++;
        b++;
        c++;
        d++;
        ++d;
        *d = sc_wrap(*a, *b, *c);
    }
    return outd;
}

PyrObject* signal_fold_f(VMGlobals* g, PyrObject* inPyrSignal, float lo, float hi) {
    float* out = (float*)(inPyrSignal->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    while (out < endptr) {
        ++out;
        *out = sc_fold(*out, lo, hi);
    }
    return inPyrSignal;
}

PyrObject* signal_fold_x(VMGlobals* g, PyrObject* ina, PyrObject* inb, PyrObject* inc) {
    PyrObject* outd;
    float *a, *b, *c, *d, *endptr;
    long minsize = sc_min(ina->size, inb->size);
    minsize = sc_min(minsize, inc->size);
    outd = newPyrSignal(g, minsize);
    a = (float*)(ina->slots) - 1;
    b = (float*)(inb->slots) - 1;
    c = (float*)(inc->slots) - 1;
    d = (float*)(outd->slots) - 1;
    endptr = d + outd->size;
    while (d < endptr) {
        a++;
        b++;
        c++;
        d++;
        *d = sc_fold(*a, *b, *c);
    }
    return outd;
}

PyrObject* signal_log(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = log(*++in);)
    return outc;
}

PyrObject* signal_log2(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = sc_log2(*++in);)
    return outc;
}

PyrObject* signal_log10(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = log10(*++in);)
    return outc;
}


PyrObject* signal_sin(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = sin(*++in);)
    return outc;
}

PyrObject* signal_cos(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = cos(*++in);)
    return outc;
}

PyrObject* signal_tan(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = tan(*++in);)
    return outc;
}

PyrObject* signal_sinh(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = sinh(*++in);)
    return outc;
}

PyrObject* signal_cosh(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = cosh(*++in);)
    return outc;
}

PyrObject* signal_tanh(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = tanh(*++in);)
    return outc;
}

PyrObject* signal_tanh_ds(PyrObject* inPyrSignal) {
    float* out = (float*)(inPyrSignal->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, ++out; *out = tanh(*out);)
    return inPyrSignal;
}

PyrObject* signal_asin(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = asin(*++in);)
    return outc;
}

PyrObject* signal_acos(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = acos(*++in);)
    return outc;
}

PyrObject* signal_atan(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = atan(*++in);)
    return outc;
}

PyrObject* signal_exp(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = exp(*++in);)
    return outc;
}

PyrObject* signal_sqrt(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    UNROLL1_CODE(inPyrSignal->size, out, *++out = sqrt(*++in);)
    return outc;
}

PyrObject* signal_distort(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    while (out < endptr) {
        float z = *++in;
        if (z < 0.f)
            *++out = z / (1.f - z);
        else
            *++out = z / (1.f + z);
    }
    return outc;
}

PyrObject* signal_softclip(VMGlobals* g, PyrObject* inPyrSignal) {
    float* in = (float*)(inPyrSignal->slots) - 1;
    PyrObject* outc = newPyrSignal(g, inPyrSignal->size);
    float* out = (float*)(outc->slots) - 1;
    float* endptr = out + inPyrSignal->size;
    while (out < endptr) {
        float z = *++in;
        if (z < -0.5f)
            *++out = (-z - .25f) / z;
        else if (z > 0.5f)
            *++out = (z - .25f) / z;
        else
            *++out = z;
    }
    return outc;
}


PyrObject* signal_rotate(VMGlobals* g, PyrObject* ina, int rot) {
    long i, j;
    PyrObject* outc = newPyrSignal(g, ina->size);
    float* a0 = (float*)(ina->slots) - 1;
    //	float *a = a0 + sc_mod(rot, ina->size);
    float* a = a0 + sc_mod(0 - rot, ina->size);
    float* aend = a0 + ina->size;
    float* c = (float*)(outc->slots) - 1;
    long nsmps = outc->size;
    for (i = 0, j = rot; i < nsmps; i++, j++) {
        *++c = *++a;
        if (a >= aend)
            a = a0;
    }
    return outc;
}

PyrObject* signal_reverse_range(PyrObject* ina, long start, long end) {
    long size = ina->size;
    long size2;
    long i;
    start = sc_max(0, start);
    end = sc_min(end + 1, size);
    size = end - start;
    size2 = size >> 1;
    float* a = (float*)(ina->slots) - 1 + start;
    //	float *b = (float*)(ina->slots) - 1 + end;
    float* b = (float*)(ina->slots) + end;
    float temp;
    for (i = 0; i < size2; ++i) {
        temp = *++a;
        *a = *--b;
        *b = temp;
    }
    return ina;
}

PyrObject* signal_normalize_range(PyrObject* ina, long start, long end) {
    long size = ina->size;
    long i;
    float z, scale, maxlevel;
    float *a0, *a;
    start = sc_max(0, start);
    end = sc_min(end + 1, size);
    size = end - start;
    a0 = (float*)(ina->slots) - 1 + start;
    a = a0;
    maxlevel = 0.f;
    for (i = 0; i < size; ++i) {
        z = fabs(*++a);
        if (z > maxlevel)
            maxlevel = z;
    }
    a = a0;
    if (maxlevel != 0.f) {
        scale = 1. / maxlevel;
        for (i = 0; i < size; ++i) {
            z = *++a;
            *a = scale * z;
        }
    }
    return ina;
}

PyrObject* signal_invert_range(PyrObject* ina, long start, long end) {
    long size = ina->size;
    long i;
    float z;
    start = sc_max(0, start);
    end = sc_min(end, size);
    size = end - start + 1;
    float* a = (float*)(ina->slots) - 1 + start;

    for (i = 0; i < size; ++i) {
        z = *++a;
        *a = -z;
    }
    return ina;
}


PyrObject* signal_fade_range(PyrObject* ina, long start, long end, float lvl0, float lvl1) {
    long size = ina->size;
    long i;
    float z, level, slope;
    start = sc_max(0, start);
    end = sc_min(end + 1, size);
    size = end - start;
    float* a = (float*)(ina->slots) - 1 + start;
    slope = (lvl1 - lvl0) / size;
    level = lvl0;
    for (i = 0; i < size; ++i) {
        z = *++a;
        *a = z * level;
        level += slope;
    }
    return ina;
}


PyrObject* signal_overdub(VMGlobals* g, PyrObject* ina, PyrObject* inb, long index) {
    float *a, *b;
    long len;

    if (index > 0) {
        a = (float*)(ina->slots) + index - 1;
        b = (float*)(inb->slots) - 1;
        len = sc_min(inb->size, ina->size - index);
    } else {
        a = (float*)(ina->slots) - 1;
        b = (float*)(inb->slots) - index - 1;
        len = sc_min(ina->size, inb->size + index);
    }

    UNROLL_CODE(len, a, *++a += *++b;);

    return ina;
}

PyrObject* signal_overwrite(VMGlobals* g, PyrObject* ina, PyrObject* inb, long index) {
    float *a, *b;
    long len;
    if (index > 0) {
        a = (float*)(ina->slots) + index - 1;
        b = (float*)(inb->slots) - 1;
        len = sc_min(inb->size, ina->size - index);
    } else {
        a = (float*)(ina->slots) - 1;
        b = (float*)(inb->slots) - index - 1;
        len = sc_min(ina->size, inb->size + index);
    }

    UNROLL_CODE(len, a, *++a = *++b;);

    return ina;
}
