/*
 *  GrainUGens.cpp
 *  xSC3plugins
 *
 *  Created by Josh Parmenter on 2/4/05.
 *  Copyright 2005 __MyCompanyName__. All rights reserved.
 *
 */
/*
    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 "SC_PlugIn.h"

#include "function_attributes.h"

static InterfaceTable* ft;

const int kMaxGrains = 64;

struct GrainInG {
    double b1, y1, y2, curamp, winPos, winInc; // envelope
    int counter, chan;
    float pan1, pan2, winType;
};

struct GrainIn : public Unit {
    int mNumActive, m_channels, mMaxGrains;
    float curtrig;
    bool mFirst;
    GrainInG* mGrains;
};

struct GrainSinG {
    double b1, y1, y2, curamp, winPos, winInc; // envelope
    int counter, chan;
    float pan1, pan2, winType;
    int32 oscphase; // the phase of the osc inside this grain
    int32 freq; // the freq of the osc inside this grain in phase inc
};

struct GrainSin : public Unit {
    int mNumActive, m_channels, mMaxGrains;
    uint32 m_lomask;
    float curtrig;
    bool mFirst;
    double m_cpstoinc, m_radtoinc;
    GrainSinG* mGrains;
};

struct GrainFMG {
    int32 coscphase, moscphase; // the phase of the osc inside this grain
    int32 mfreq; // the freq of the osc inside this grain in phase inc
    double b1, y1, y2, curamp, winPos, winInc; // envelope
    float deviation, carbase, pan1, pan2, winType;
    int counter, chan;
};


struct GrainFM : public Unit {
    int mNumActive, m_channels, mMaxGrains;
    uint32 m_lomask;
    float curtrig;
    bool mFirst;
    double m_cpstoinc, m_radtoinc;
    GrainFMG* mGrains;
};

struct GrainBufG {
    double phase, rate;
    double b1, y1, y2, curamp, winPos, winInc;
    float pan1, pan2, winType;
    int counter, chan, bufnum, interp;
};

struct GrainBuf : public Unit {
    int mNumActive, m_channels, mMaxGrains;
    float curtrig;
    GrainBufG* mGrains;
};

struct WarpWinGrain {
    double phase, rate;
    double winPos, winInc, b1, y1, y2, curamp; // tells the grain where to look in the winBuf for an amp value
    int counter, bufnum, interp;
    float winType;
};

struct Warp1 : public Unit {
    float m_fbufnum;
    SndBuf* m_buf;
    int mNumActive[16];
    int mNextGrain[16];
    WarpWinGrain mGrains[16][kMaxGrains];
};

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

extern "C" {

void GrainIn_Ctor(GrainIn* unit);
void GrainIn_Dtor(GrainIn* unit);
void GrainIn_next_a(GrainIn* unit, int inNumSamples);
void GrainIn_next_k(GrainIn* unit, int inNumSamples);

void GrainSin_Ctor(GrainSin* unit);
void GrainSin_Dtor(GrainSin* unit);
void GrainSin_next_a(GrainSin* unit, int inNumSamples);
void GrainSin_next_k(GrainSin* unit, int inNumSamples);

void GrainFM_Ctor(GrainFM* unit);
void GrainFM_Dtor(GrainFM* unit);
void GrainFM_next_a(GrainFM* unit, int inNumSamples);
void GrainFM_next_k(GrainFM* unit, int inNumSamples);

void GrainBuf_Ctor(GrainBuf* unit);
void GrainBuf_Dtor(GrainBuf* unit);

void Warp1_next(Warp1* unit, int inNumSamples);
void Warp1_Ctor(Warp1* unit);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// Granular UGens ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////

inline float GRAIN_IN_AT(Unit* unit, int index, int offset) {
    if (INRATE(index) == calc_FullRate)
        return IN(index)[offset];
    if (INRATE(index) == calc_DemandRate)
        return DEMANDINPUT_A(index, offset + 1);
    return IN0(index);
}

template <bool full_rate> inline float grain_in_at(Unit* unit, int index, int offset) {
    if (full_rate)
        return GRAIN_IN_AT(unit, index, offset);

    if (INRATE(index) == calc_DemandRate)
        return DEMANDINPUT_A(index, offset + 1);
    else
        return IN0(index);
}

inline double sc_gloop(double in, double hi) {
    // avoid the divide if possible
    if (in >= hi) {
        in -= hi;
        if (in < hi)
            return in;
    } else if (in < 0.) {
        in += hi;
        if (in >= 0.)
            return in;
    } else
        return in;

    return in - hi * floor(in / hi);
}

#define GRAIN_BUF                                                                                                      \
    const SndBuf* buf;                                                                                                 \
    if (bufnum >= world->mNumSndBufs) {                                                                                \
        int localBufNum = bufnum - world->mNumSndBufs;                                                                 \
        Graph* parent = unit->mParent;                                                                                 \
        if (localBufNum <= parent->localBufNum) {                                                                      \
            buf = parent->mLocalSndBufs + localBufNum;                                                                 \
        } else {                                                                                                       \
            bufnum = 0;                                                                                                \
            buf = world->mSndBufs + bufnum;                                                                            \
        }                                                                                                              \
    } else {                                                                                                           \
        if (bufnum < 0) {                                                                                              \
            bufnum = 0;                                                                                                \
        }                                                                                                              \
        buf = world->mSndBufs + bufnum;                                                                                \
    }                                                                                                                  \
    LOCK_SNDBUF_SHARED(buf);                                                                                           \
    const float* bufData __attribute__((__unused__)) = buf->data;                                                      \
    uint32 bufChannels __attribute__((__unused__)) = buf->channels;                                                    \
    uint32 bufSamples __attribute__((__unused__)) = buf->samples;                                                      \
    uint32 bufFrames = buf->frames;                                                                                    \
    int guardFrame __attribute__((__unused__)) = bufFrames - 2;

#define DECLARE_WINDOW                                                                                                 \
    double winPos, winInc, w, b1, y1, y2, y0;                                                                          \
    float amp;                                                                                                         \
    winPos = winInc = w = b1 = y1 = y2 = y0 = amp = 0.;                                                                \
    SndBuf* window;                                                                                                    \
    const float* windowData __attribute__((__unused__)) = 0;                                                           \
    uint32 windowSamples __attribute__((__unused__)) = 0;                                                              \
    uint32 windowFrames __attribute__((__unused__)) = 0;                                                               \
    int windowGuardFrame = 0;


#define CHECK_BUF                                                                                                      \
    if (!bufData) {                                                                                                    \
        unit->mDone = true;                                                                                            \
        ClearUnitOutputs(unit, inNumSamples);                                                                          \
        return;                                                                                                        \
    }

#define GET_GRAIN_WIN_RELAXED(WINTYPE)                                                                                 \
    do {                                                                                                               \
        assert(WINTYPE < unit->mWorld->mNumSndBufs);                                                                   \
        window = unit->mWorld->mSndBufs + (int)WINTYPE;                                                                \
        windowData = window->data;                                                                                     \
        windowSamples = window->samples;                                                                               \
        windowFrames = window->frames;                                                                                 \
        windowGuardFrame = windowFrames - 1;                                                                           \
    } while (0);


static inline bool getGrainWin(Unit* unit, float wintype, SndBuf*& window, const float*& windowData,
                               uint32& windowSamples, uint32& windowFrames, int& windowGuardFrame) {
    if (wintype >= unit->mWorld->mNumSndBufs) {
        Print("Envelope buffer out of range!\n");
        return false;
    }

    assert(wintype < unit->mWorld->mNumSndBufs);

    if (wintype < 0)
        return true; // use default hann window

    window = unit->mWorld->mSndBufs + (int)wintype;
    windowData = window->data;
    if (!windowData)
        return false;

    windowSamples = window->samples;
    windowFrames = window->frames;
    windowGuardFrame = windowFrames - 1;

    return true;
}

#define GRAIN_LOOP_BODY_4                                                                                              \
    float amp = y1 * y1;                                                                                               \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float* table1 = bufData + iphase;                                                                                  \
    float* table0 = table1 - 1;                                                                                        \
    float* table2 = table1 + 1;                                                                                        \
    float* table3 = table1 + 2;                                                                                        \
    if (iphase == 0) {                                                                                                 \
        table0 += bufSamples;                                                                                          \
    } else if (iphase >= guardFrame) {                                                                                 \
        if (iphase == guardFrame) {                                                                                    \
            table3 -= bufSamples;                                                                                      \
        } else {                                                                                                       \
            table2 -= bufSamples;                                                                                      \
            table3 -= bufSamples;                                                                                      \
        }                                                                                                              \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float a = table0[0];                                                                                               \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float d = table3[0];                                                                                               \
    float outval = amp * cubicinterp(fracphase, a, b, c, d) ZXP(out1) += outval;                                       \
    double y0 = b1 * y1 - y2;                                                                                          \
    y2 = y1;                                                                                                           \
    y1 = y0;

#define GRAIN_LOOP_BODY_2                                                                                              \
    float amp = y1 * y1;                                                                                               \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float* table1 = bufData + iphase;                                                                                  \
    float* table2 = table1 + 1;                                                                                        \
    if (iphase > guardFrame) {                                                                                         \
        table2 -= bufSamples;                                                                                          \
    }                                                                                                                  \
    double fracphase = phase - (double)iphase;                                                                         \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float outval = amp * (b + fracphase * (c - b));                                                                    \
    ZXP(out1) += outval;                                                                                               \
    double y0 = b1 * y1 - y2;                                                                                          \
    y2 = y1;                                                                                                           \
    y1 = y0;


#define GRAIN_LOOP_BODY_1                                                                                              \
    float amp = y1 * y1;                                                                                               \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float outval = amp * bufData[iphase];                                                                              \
    ZXP(out1) += outval;                                                                                               \
    double y0 = b1 * y1 - y2;                                                                                          \
    y2 = y1;                                                                                                           \
    y1 = y0;

#define BUF_GRAIN_AMP                                                                                                  \
    winPos += winInc;                                                                                                  \
    int iWinPos = (int)winPos;                                                                                         \
    double winFrac = winPos - (double)iWinPos;                                                                         \
    float* winTable1 = windowData + iWinPos;                                                                           \
    float* winTable2 = winTable1 + 1;                                                                                  \
    if (winPos > windowGuardFrame) {                                                                                   \
        winTable2 -= windowSamples;                                                                                    \
    }                                                                                                                  \
    amp = lininterp(winFrac, winTable1[0], winTable2[0]);

#define BUF_GRAIN_LOOP_BODY_4                                                                                          \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float* table1 = bufData + iphase;                                                                                  \
    float* table0 = table1 - 1;                                                                                        \
    float* table2 = table1 + 1;                                                                                        \
    float* table3 = table1 + 2;                                                                                        \
    if (iphase == 0) {                                                                                                 \
        table0 += bufSamples;                                                                                          \
    } else if (iphase >= guardFrame) {                                                                                 \
        if (iphase == guardFrame) {                                                                                    \
            table3 -= bufSamples;                                                                                      \
        } else {                                                                                                       \
            table2 -= bufSamples;                                                                                      \
            table3 -= bufSamples;                                                                                      \
        }                                                                                                              \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float a = table0[0];                                                                                               \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float d = table3[0];                                                                                               \
    float outval = amp * cubicinterp(fracphase, a, b, c, d);                                                           \
    ZXP(out1) += outval;

#define BUF_GRAIN_LOOP_BODY_2                                                                                          \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float* table1 = bufData + iphase;                                                                                  \
    float* table2 = table1 + 1;                                                                                        \
    if (iphase > guardFrame) {                                                                                         \
        table2 -= bufSamples;                                                                                          \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float outval = amp * (b + fracphase * (c - b));                                                                    \
    ZXP(out1) += outval;

// amp needs to be calculated by looking up values in window

#define BUF_GRAIN_LOOP_BODY_1                                                                                          \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float outval = amp * bufData[iphase];                                                                              \
    ZXP(out1) += outval;


#define SETUP_OUT                                                                                                      \
    uint32 numOutputs = unit->mNumOutputs;                                                                             \
    if (numOutputs > bufChannels) {                                                                                    \
        unit->mDone = true;                                                                                            \
        ClearUnitOutputs(unit, inNumSamples);                                                                          \
        return;                                                                                                        \
    }                                                                                                                  \
    float* out[16];                                                                                                    \
    for (uint32 i = 0; i < numOutputs; ++i)                                                                            \
        out[i] = ZOUT(i);


#define CALC_GRAIN_PAN                                                                                                 \
    float panangle, pan1, pan2;                                                                                        \
    float *out1, *out2;                                                                                                \
    if (numOutputs > 1) {                                                                                              \
        if (numOutputs > 2) {                                                                                          \
            pan = sc_wrap(pan * 0.5f, 0.f, 1.f);                                                                       \
            float cpan = numOutputs * pan + 0.5f;                                                                      \
            float ipan = floor(cpan);                                                                                  \
            float panfrac = cpan - ipan;                                                                               \
            panangle = panfrac * pi2_f;                                                                                \
            grain->chan = (int)ipan;                                                                                   \
            if (grain->chan >= (int)numOutputs)                                                                        \
                grain->chan -= numOutputs;                                                                             \
        } else {                                                                                                       \
            grain->chan = 0;                                                                                           \
            pan = sc_clip(pan * 0.5f + 0.5f, 0.f, 1.f);                                                                \
            panangle = pan * pi2_f;                                                                                    \
        }                                                                                                              \
        pan1 = grain->pan1 = cos(panangle);                                                                            \
        pan2 = grain->pan2 = sin(panangle);                                                                            \
    } else {                                                                                                           \
        grain->chan = 0;                                                                                               \
        pan1 = grain->pan1 = 1.;                                                                                       \
        pan2 = grain->pan2 = 0.;                                                                                       \
    }

#define GET_GRAIN_INIT_AMP                                                                                             \
    if (grain->winType < 0.) {                                                                                         \
        w = pi / counter;                                                                                              \
        b1 = grain->b1 = 2. * cos(w);                                                                                  \
        y1 = sin(w);                                                                                                   \
        y2 = 0.;                                                                                                       \
        amp = y1 * y1;                                                                                                 \
    } else {                                                                                                           \
        amp = windowData[0];                                                                                           \
        winPos = grain->winPos = 0.f;                                                                                  \
        winInc = grain->winInc = (double)windowSamples / counter;                                                      \
    }

#define CALC_NEXT_GRAIN_AMP_INTERNAL                                                                                   \
    do {                                                                                                               \
        y0 = b1 * y1 - y2;                                                                                             \
        y2 = y1;                                                                                                       \
        y1 = y0;                                                                                                       \
        amp = y1 * y1;                                                                                                 \
    } while (0)

#define CALC_NEXT_GRAIN_AMP_CUSTOM                                                                                     \
    do {                                                                                                               \
        winPos += winInc;                                                                                              \
        int iWinPos = (int)winPos;                                                                                     \
        double winFrac = winPos - (double)iWinPos;                                                                     \
        const float* winTable1 = windowData + iWinPos;                                                                 \
        const float* winTable2 = winTable1 + 1;                                                                        \
        if (!windowData)                                                                                               \
            break;                                                                                                     \
        if (winPos > windowGuardFrame)                                                                                 \
            winTable2 -= windowSamples;                                                                                \
        amp = lininterp(winFrac, winTable1[0], winTable2[0]);                                                          \
    } while (0);                                                                                                       \
    if (!windowData)                                                                                                   \
        break;

#define CALC_NEXT_GRAIN_AMP                                                                                            \
    if (grain->winType < 0.) {                                                                                         \
        CALC_NEXT_GRAIN_AMP_INTERNAL;                                                                                  \
    } else {                                                                                                           \
        CALC_NEXT_GRAIN_AMP_CUSTOM;                                                                                    \
    }


#define GET_GRAIN_AMP_PARAMS                                                                                           \
    if (grain->winType < 0.) {                                                                                         \
        b1 = grain->b1;                                                                                                \
        y1 = grain->y1;                                                                                                \
        y2 = grain->y2;                                                                                                \
        amp = grain->curamp;                                                                                           \
    } else {                                                                                                           \
        GET_GRAIN_WIN_RELAXED(grain->winType);                                                                         \
        if (!windowData)                                                                                               \
            break;                                                                                                     \
        winPos = grain->winPos;                                                                                        \
        winInc = grain->winInc;                                                                                        \
        amp = grain->curamp;                                                                                           \
    }

#define SAVE_GRAIN_AMP_PARAMS                                                                                          \
    grain->y1 = y1;                                                                                                    \
    grain->y2 = y2;                                                                                                    \
    grain->winPos = winPos;                                                                                            \
    grain->winInc = winInc;                                                                                            \
    grain->curamp = amp;                                                                                               \
    grain->counter -= nsmps;

#define WRAP_CHAN(offset)                                                                                              \
    out1 = OUT(grain->chan) + offset;                                                                                  \
    if (numOutputs > 1) {                                                                                              \
        if ((grain->chan + 1) >= (int)numOutputs)                                                                      \
            out2 = OUT(0) + offset;                                                                                    \
        else                                                                                                           \
            out2 = OUT(grain->chan + 1) + offset;                                                                      \
    }

#define GET_PAN_PARAMS                                                                                                 \
    float pan1 = grain->pan1;                                                                                          \
    uint32 chan1 = grain->chan;                                                                                        \
    float* out1 = OUT(chan1);                                                                                          \
    if (numOutputs > 1) {                                                                                              \
        pan2 = grain->pan2;                                                                                            \
        uint32 chan2 = chan1 + 1;                                                                                      \
        if (chan2 >= numOutputs)                                                                                       \
            chan2 = 0;                                                                                                 \
        out2 = OUT(chan2);                                                                                             \
    }


//////////////////// InGrain ////////////////////

inline void GrainIn_next_play_active(GrainIn* unit, int inNumSamples) {
    const uint32 numOutputs = unit->mNumOutputs;
    float* in = IN(2);
    for (int i = 0; i < unit->mNumActive;) {
        GrainInG* grain = unit->mGrains + i;
        DECLARE_WINDOW
        GET_GRAIN_AMP_PARAMS
        // begin add //

        float pan2 = 0.f;
        float* out2;

        GET_PAN_PARAMS
        // end add //
        int nsmps = sc_min(grain->counter, inNumSamples);
        for (int j = 0; j < nsmps; ++j) {
            float outval = amp * in[j];
            // begin change / add //
            out1[j] += outval * pan1;
            if (numOutputs > 1)
                out2[j] += outval * pan2;
            // end change //

            CALC_NEXT_GRAIN_AMP
        }

        SAVE_GRAIN_AMP_PARAMS

        if (grain->counter <= 0)
            *grain = unit->mGrains[--unit->mNumActive]; // remove grain
        else
            ++i;
    }
}

template <bool full_rate> inline void GrainIn_next_start_new(GrainIn* unit, int inNumSamples, int position) {
    if (unit->mNumActive + 1 >= unit->mMaxGrains) {
        Print("Too many grains!\n");
        return;
    }

    float winType = grain_in_at<full_rate>(unit, 4, position);
    DECLARE_WINDOW
    bool success = getGrainWin(unit, winType, window, windowData, windowSamples, windowFrames, windowGuardFrame);
    if (!success)
        return;

    GrainInG* grain = unit->mGrains + unit->mNumActive++;
    float winSize = grain_in_at<full_rate>(unit, 1, position);
    double counter = winSize * SAMPLERATE;
    counter = sc_max(4., counter);
    grain->counter = (int)counter;
    grain->winType = winType;

    GET_GRAIN_INIT_AMP

    const uint32 numOutputs = unit->mNumOutputs;
    float* in = IN(2);

    float* in1 = in + position;
    // begin add //
    float pan = grain_in_at<full_rate>(unit, 3, position);

    CALC_GRAIN_PAN

    WRAP_CHAN(position)

    int nsmps = sc_min(grain->counter, inNumSamples - position);
    for (int j = 0; j < nsmps; ++j) {
        float outval = amp * in1[j];
        // begin add / change
        out1[j] += outval * pan1;
        if (numOutputs > 1)
            out2[j] += outval * pan2;
        // end add / change
        CALC_NEXT_GRAIN_AMP
    }

    SAVE_GRAIN_AMP_PARAMS

    if (grain->counter <= 0)
        *grain = unit->mGrains[--unit->mNumActive]; // remove grain
}

void GrainIn_next_a(GrainIn* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);
    // begin add
    // end add

    GrainIn_next_play_active(unit, inNumSamples);

    float* trig = IN(0);
    for (int i = 0; i < inNumSamples; ++i) {
        if ((unit->curtrig <= 0) && (trig[i] > 0.0))
            GrainIn_next_start_new<true>(unit, inNumSamples, i);
        unit->curtrig = trig[i];
    }
}

void GrainIn_next_k(GrainIn* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);
    if (unit->mFirst) {
        unit->mFirst = false;
        float maxGrains = IN0(5);
        unit->mMaxGrains = (int)maxGrains;
        unit->mGrains = (GrainInG*)RTAlloc(unit->mWorld, unit->mMaxGrains * sizeof(GrainInG));
        ClearUnitIfMemFailed(unit->mGrains);
    }

    GrainIn_next_play_active(unit, inNumSamples);

    float trig = IN0(0);

    if ((unit->curtrig <= 0) && (trig > 0.0))
        GrainIn_next_start_new<false>(unit, inNumSamples, 0);
    unit->curtrig = trig;
}


void GrainIn_Ctor(GrainIn* unit) {
    if (INRATE(0) == calc_FullRate)
        SETCALC(GrainIn_next_a);
    else
        SETCALC(GrainIn_next_k);
    unit->mFirst = true;
    unit->mNumActive = 0;
    unit->curtrig = 0.f;
    unit->mGrains = nullptr;
    GrainIn_next_k(unit, 1);
}

void GrainIn_Dtor(GrainIn* unit) { RTFree(unit->mWorld, unit->mGrains); }

inline void GrainSin_next_play_active(GrainSin* unit, int inNumSamples) {
    const unsigned int numOutputs = unit->mNumOutputs;

    float* table0 = ft->mSineWavetable;
    float* table1 = table0 + 1;

    for (int i = 0; i < unit->mNumActive;) {
        GrainSinG* grain = unit->mGrains + i;
        DECLARE_WINDOW
        GET_GRAIN_AMP_PARAMS

        // begin add //

        float pan2 = 0.f;
        float* out2;

        GET_PAN_PARAMS

        // end add //

        int32 thisfreq = grain->freq;
        int32 oscphase = grain->oscphase;

        int nsmps = sc_min(grain->counter, inNumSamples);
        for (int j = 0; j < nsmps; ++j) {
            float outval = amp * lookupi1(table0, table1, oscphase, unit->m_lomask);
            // begin change / add //
            out1[j] += outval * pan1;
            if (numOutputs > 1)
                out2[j] += outval * pan2;
            // end change //
            CALC_NEXT_GRAIN_AMP

            oscphase += thisfreq;
        }

        SAVE_GRAIN_AMP_PARAMS

        grain->oscphase = oscphase;
        if (grain->counter <= 0)
            *grain = unit->mGrains[--unit->mNumActive]; // remove grain
        else
            ++i;
    }
}

template <bool full_rate> inline void GrainSin_next_start_new(GrainSin* unit, int inNumSamples, int position) {
    if (unit->mNumActive + 1 >= unit->mMaxGrains) {
        Print("Too many grains!\n");
        return;
    }

    float winType = grain_in_at<full_rate>(unit, 4, position);
    DECLARE_WINDOW
    bool success = getGrainWin(unit, winType, window, windowData, windowSamples, windowFrames, windowGuardFrame);
    if (!success)
        return;

    GrainSinG* grain = unit->mGrains + unit->mNumActive++;
    float freq = grain_in_at<full_rate>(unit, 2, position);
    float winSize = grain_in_at<full_rate>(unit, 1, position);
    int32 thisfreq = grain->freq = (int32)(unit->m_cpstoinc * freq);
    int32 oscphase = 0;
    double counter = winSize * SAMPLERATE;
    counter = sc_max(4., counter);
    grain->counter = (int)counter;
    grain->winType = winType;

    GET_GRAIN_INIT_AMP

    const uint32 numOutputs = unit->mNumOutputs;

    float* table0 = ft->mSineWavetable;
    float* table1 = table0 + 1;

    // begin add //
    float pan = grain_in_at<full_rate>(unit, 3, position);

    CALC_GRAIN_PAN

    WRAP_CHAN(position)

    // end add //

    int nsmps = sc_min(grain->counter, inNumSamples - position);
    for (int j = 0; j < nsmps; ++j) {
        float outval = amp * lookupi1(table0, table1, oscphase, unit->m_lomask);
        // begin add / change
        out1[j] += outval * pan1;
        if (numOutputs > 1)
            out2[j] += outval * pan2;
        // end add / change
        CALC_NEXT_GRAIN_AMP

        oscphase += thisfreq;
    }
    grain->oscphase = oscphase;

    SAVE_GRAIN_AMP_PARAMS

    if (grain->counter <= 0)
        *grain = unit->mGrains[--unit->mNumActive]; // remove grain
}

void GrainSin_next_a(GrainSin* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    GrainSin_next_play_active(unit, inNumSamples);

    float* trig = IN(0);
    for (int i = 0; i < inNumSamples; ++i) {
        if ((unit->curtrig <= 0) && (trig[i] > 0.0))
            GrainSin_next_start_new<true>(unit, inNumSamples, i);
        unit->curtrig = trig[i];
    }
}

void GrainSin_next_k(GrainSin* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);
    if (unit->mFirst) {
        unit->mFirst = false;
        float maxGrains = IN0(5);
        unit->mMaxGrains = (int)maxGrains;
        unit->mGrains = (GrainSinG*)RTAlloc(unit->mWorld, unit->mMaxGrains * sizeof(GrainSinG));
        ClearUnitIfMemFailed(unit->mGrains);
    }

    GrainSin_next_play_active(unit, inNumSamples);

    float trig = IN0(0);


    if ((unit->curtrig <= 0) && (trig > 0.0))
        GrainSin_next_start_new<false>(unit, inNumSamples, 0);
    unit->curtrig = trig;
}

void GrainSin_Ctor(GrainSin* unit) {
    if (INRATE(0) == calc_FullRate)
        SETCALC(GrainSin_next_a);
    else
        SETCALC(GrainSin_next_k);
    int tableSizeSin = ft->mSineSize;
    unit->m_lomask = (tableSizeSin - 1) << 3;
    unit->m_radtoinc = tableSizeSin * (rtwopi * 65536.);
    unit->m_cpstoinc = tableSizeSin * SAMPLEDUR * 65536.;
    unit->curtrig = 0.f;
    unit->mNumActive = 0;
    unit->mFirst = true;
    unit->mGrains = nullptr;
    GrainSin_next_k(unit, 1);
}

void GrainSin_Dtor(GrainSin* unit) { RTFree(unit->mWorld, unit->mGrains); }


inline void GrainFM_next_play_active(GrainFM* unit, int inNumSamples) {
    const uint32 numOutputs = unit->mNumOutputs;
    // end add

    float* table0 = ft->mSineWavetable;
    float* table1 = table0 + 1;

    for (int i = 0; i < unit->mNumActive;) {
        GrainFMG* grain = unit->mGrains + i;
        DECLARE_WINDOW
        GET_GRAIN_AMP_PARAMS

        int32 mfreq = grain->mfreq;
        int32 moscphase = grain->moscphase;
        int32 coscphase = grain->coscphase;
        float deviation = grain->deviation;
        float carbase = grain->carbase;
        // begin add //

        float pan2 = 0.f;
        float* out2;

        GET_PAN_PARAMS

        // end add //
        int nsmps = sc_min(grain->counter, inNumSamples);
        for (int j = 0; j < nsmps; ++j) {
            float thismod = lookupi1(table0, table1, moscphase, unit->m_lomask) * deviation;
            float outval = amp * lookupi1(table0, table1, coscphase, unit->m_lomask);
            // begin change / add //
            out1[j] += outval * pan1;
            if (numOutputs > 1)
                out2[j] += outval * pan2;
            // end change //
            CALC_NEXT_GRAIN_AMP

            int32 cfreq = (int32)(unit->m_cpstoinc * (carbase + thismod)); // needs to be calced in the loop!
            coscphase += cfreq;
            moscphase += mfreq;
        } // need to save float carbase, int32 mfreq, float deviation
        grain->coscphase = coscphase;
        grain->moscphase = moscphase;

        SAVE_GRAIN_AMP_PARAMS

        if (grain->counter <= 0)
            *grain = unit->mGrains[--unit->mNumActive]; // remove grain
        else
            ++i;
    }
}

template <bool full_rate> inline void GrainFM_next_start_new(GrainFM* unit, int inNumSamples, int position) {
    if (unit->mNumActive + 1 >= unit->mMaxGrains) {
        Print("Too many grains!\n");
        return;
    }

    float winType = grain_in_at<full_rate>(unit, 6, position);
    DECLARE_WINDOW
    bool success = getGrainWin(unit, winType, window, windowData, windowSamples, windowFrames, windowGuardFrame);
    if (!success)
        return;

    GrainFMG* grain = unit->mGrains + unit->mNumActive++;
    float winSize = GRAIN_IN_AT(unit, 1, position);
    float carfreq = GRAIN_IN_AT(unit, 2, position);
    float modfreq = GRAIN_IN_AT(unit, 3, position);
    float index = GRAIN_IN_AT(unit, 4, position);
    float deviation = grain->deviation = index * modfreq;
    int32 mfreq = grain->mfreq = (int32)(unit->m_cpstoinc * modfreq);
    grain->carbase = carfreq;
    int32 coscphase = 0;
    int32 moscphase = 0;
    double counter = winSize * SAMPLERATE;
    counter = sc_max(4., counter);
    grain->counter = (int)counter;
    grain->winType = winType; // GRAIN_IN_AT(unit, 6, i);

    GET_GRAIN_INIT_AMP
    const uint32 numOutputs = unit->mNumOutputs;

    float* table0 = ft->mSineWavetable;
    float* table1 = table0 + 1;

    // begin add //
    float pan = GRAIN_IN_AT(unit, 5, position);

    CALC_GRAIN_PAN

    WRAP_CHAN(position)

    // end add //
    int nsmps = sc_min(grain->counter, inNumSamples - position);
    for (int j = 0; j < nsmps; ++j) {
        float thismod = lookupi1(table0, table1, moscphase, unit->m_lomask) * deviation;
        float outval = amp * lookupi1(table0, table1, coscphase, unit->m_lomask);
        // begin add / change
        out1[j] += outval * pan1;
        if (numOutputs > 1)
            out2[j] += outval * pan2;
        // end add / change
        CALC_NEXT_GRAIN_AMP

        int32 cfreq = (int32)(unit->m_cpstoinc * (carfreq + thismod)); // needs to be calced in the loop!
        coscphase += cfreq;
        moscphase += mfreq;
    } // need to save float carbase, int32 mfreq, float deviation
    grain->coscphase = coscphase;
    grain->moscphase = moscphase;

    SAVE_GRAIN_AMP_PARAMS

    if (grain->counter <= 0)
        *grain = unit->mGrains[--unit->mNumActive]; // remove grain
}

void GrainFM_next_a(GrainFM* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    GrainFM_next_play_active(unit, inNumSamples);

    float* trig = IN(0);
    for (int i = 0; i < inNumSamples; ++i) {
        if ((unit->curtrig <= 0) && (trig[i] > 0.0))
            GrainFM_next_start_new<true>(unit, inNumSamples, i);
        unit->curtrig = trig[i];
    }
}

void GrainFM_next_k(GrainFM* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    if (unit->mFirst) {
        unit->mFirst = false;
        float maxGrains = IN0(7);
        unit->mMaxGrains = (int)maxGrains;
        unit->mGrains = (GrainFMG*)RTAlloc(unit->mWorld, unit->mMaxGrains * sizeof(GrainFMG));
        ClearUnitIfMemFailed(unit->mGrains);
    }

    GrainFM_next_play_active(unit, inNumSamples);

    float trig = IN0(0);
    if ((unit->curtrig <= 0) && (trig > 0.0))
        GrainFM_next_start_new<false>(unit, inNumSamples, 0);

    unit->curtrig = trig;
}

void GrainFM_Ctor(GrainFM* unit) {
    if (INRATE(0) == calc_FullRate)
        SETCALC(GrainFM_next_a);
    else
        SETCALC(GrainFM_next_k);
    int tableSizeSin = ft->mSineSize;
    unit->m_lomask = (tableSizeSin - 1) << 3;
    unit->m_radtoinc = tableSizeSin * (rtwopi * 65536.);
    unit->m_cpstoinc = tableSizeSin * SAMPLEDUR * 65536.;
    unit->curtrig = 0.f;
    unit->mNumActive = 0;
    unit->mFirst = true;
    unit->mGrains = nullptr;
    GrainFM_next_k(unit, 1);
}

void GrainFM_Dtor(GrainFM* unit) { RTFree(unit->mWorld, unit->mGrains); }

#define GRAIN_BUF_LOOP_BODY_4_MONO                                                                                     \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    const float* table1 = bufData + iphase;                                                                            \
    const float* table0 = table1 - 1;                                                                                  \
    const float* table2 = table1 + 1;                                                                                  \
    const float* table3 = table1 + 2;                                                                                  \
    if (iphase == 0) {                                                                                                 \
        table0 += bufSamples;                                                                                          \
    } else if (iphase >= guardFrame) {                                                                                 \
        if (iphase == guardFrame) {                                                                                    \
            table3 -= bufSamples;                                                                                      \
        } else {                                                                                                       \
            table2 -= bufSamples;                                                                                      \
            table3 -= bufSamples;                                                                                      \
        }                                                                                                              \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float a = table0[0];                                                                                               \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float d = table3[0];                                                                                               \
    float outval = amp * cubicinterp(fracphase, a, b, c, d);                                                           \
    out1[j] += outval * pan1;

#define GRAIN_BUF_LOOP_BODY_4_STEREO                                                                                   \
    GRAIN_BUF_LOOP_BODY_4_MONO                                                                                         \
    out2[j] += outval * pan2;

#define GRAIN_BUF_LOOP_BODY_2_MONO                                                                                     \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    const float* table1 = bufData + iphase;                                                                            \
    const float* table2 = table1 + 1;                                                                                  \
    if (iphase > guardFrame) {                                                                                         \
        table2 -= bufSamples;                                                                                          \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float b = table1[0];                                                                                               \
    float c = table2[0];                                                                                               \
    float outval = amp * (b + fracphase * (c - b));                                                                    \
    out1[j] += outval * pan1;

#define GRAIN_BUF_LOOP_BODY_2_STEREO                                                                                   \
    GRAIN_BUF_LOOP_BODY_2_MONO                                                                                         \
    out2[j] += outval * pan2;

#define GRAIN_BUF_LOOP_BODY_1_MONO                                                                                     \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float outval = amp * bufData[iphase];                                                                              \
    out1[j] += outval * pan1;

#define GRAIN_BUF_LOOP_BODY_1_STEREO                                                                                   \
    GRAIN_BUF_LOOP_BODY_1_MONO                                                                                         \
    out2[j] += outval * pan2;


#define GRAIN_BUF_PLAY_GRAIN(WINDOW)                                                                                   \
    do {                                                                                                               \
        if (numOutputs == 1) {                                                                                         \
            if (grain->interp >= 4) {                                                                                  \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_4_MONO                                                                         \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            } else if (grain->interp >= 2) {                                                                           \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_2_MONO                                                                         \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            } else {                                                                                                   \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_1_MONO                                                                         \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            }                                                                                                          \
        } else {                                                                                                       \
            if (grain->interp >= 4) {                                                                                  \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_4_STEREO                                                                       \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            } else if (grain->interp >= 2) {                                                                           \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_2_STEREO                                                                       \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            } else {                                                                                                   \
                for (int j = 0; j < nsmps; j++) {                                                                      \
                    GRAIN_BUF_LOOP_BODY_1_STEREO                                                                       \
                    CALC_NEXT_GRAIN_AMP_##WINDOW;                                                                      \
                    phase += rate;                                                                                     \
                }                                                                                                      \
            }                                                                                                          \
        }                                                                                                              \
    } while (0)


static inline bool GrainBuf_grain_cleanup(GrainBuf* unit, GrainBufG* grain) {
    if (grain->counter <= 0) {
        *grain = unit->mGrains[--unit->mNumActive]; // remove grain
        return true;
    } else
        return false;
}

template <bool IsMono> static inline void GrainBuf_next_play_active(GrainBuf* unit, int inNumSamples) {
    const uint32 numOutputs = IsMono ? 1 : unit->mNumOutputs;

    World* world = unit->mWorld;

    for (int i = 0; i < unit->mNumActive;) {
        GrainBufG* grain = unit->mGrains + i;
        int bufnum = grain->bufnum;

        GRAIN_BUF

        if (!bufData || (bufChannels != 1)) {
            grain->counter -= inNumSamples;
            if (!GrainBuf_grain_cleanup(unit, grain))
                ++i;
            continue;
        }

        double loopMax = (double)bufFrames;
        double rate = grain->rate;
        double phase = grain->phase;

        DECLARE_WINDOW
        GET_GRAIN_AMP_PARAMS


        // begin add //
        float pan2 = 0.f;
        float* out2;
        GET_PAN_PARAMS
        // end add //
        int nsmps = sc_min(grain->counter, inNumSamples);

        if (grain->winType < 0.)
            GRAIN_BUF_PLAY_GRAIN(INTERNAL);
        else
            GRAIN_BUF_PLAY_GRAIN(CUSTOM);

        if (GrainBuf_grain_cleanup(unit, grain))
            continue;
        ++i;

        grain->phase = phase;

        SAVE_GRAIN_AMP_PARAMS
    }
}

template <bool full_rate, bool IsMono>
static inline void GrainBuf_next_start_new(GrainBuf* unit, int inNumSamples, int position) {
    World* world = unit->mWorld;

    if (unit->mNumActive + 1 >= unit->mMaxGrains) {
        Print("Too many grains!\n");
        return;
    }

    float winType = grain_in_at<full_rate>(unit, 7, position);
    DECLARE_WINDOW
    bool success = getGrainWin(unit, winType, window, windowData, windowSamples, windowFrames, windowGuardFrame);
    if (!success)
        return;

    GrainBufG* grain = unit->mGrains + unit->mNumActive++;
    int32 bufnum = grain_in_at<full_rate>(unit, 2, position);
    grain->bufnum = bufnum;

    GRAIN_BUF

    if ((bufChannels != 1) || (!bufData)) {
        GrainBuf_grain_cleanup(unit, grain);
        return;
    }

    float bufSampleRate = buf->samplerate;
    float bufRateScale = bufSampleRate * SAMPLEDUR;
    double loopMax = (double)bufFrames;

    double rate = grain->rate = grain_in_at<full_rate>(unit, 3, position) * bufRateScale;
    double phase = grain_in_at<full_rate>(unit, 4, position) * bufFrames;
    if (!sc_isfinite(phase)) {
        GrainBuf_grain_cleanup(unit, grain);
        return;
    }

    double counter = grain_in_at<full_rate>(unit, 1, position) * SAMPLERATE;
    counter = sc_max(4., counter);
    grain->counter = (int)counter;

    grain->interp = (int)grain_in_at<full_rate>(unit, 5, position);
    grain->winType = winType;

    GET_GRAIN_INIT_AMP

    const uint32 numOutputs = IsMono ? 1 : unit->mNumOutputs;

    // begin add //
    float pan = grain_in_at<full_rate>(unit, 6, position);

    CALC_GRAIN_PAN

    WRAP_CHAN(position)

    // end add //

    int nsmps = sc_min(grain->counter, inNumSamples - position);

    if (grain->winType < 0.)
        GRAIN_BUF_PLAY_GRAIN(INTERNAL);
    else
        GRAIN_BUF_PLAY_GRAIN(CUSTOM);

    grain->phase = phase;

    SAVE_GRAIN_AMP_PARAMS

    GrainBuf_grain_cleanup(unit, grain);
}


template <bool MultiChannel> static void GrainBuf_next_a(GrainBuf* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    GrainBuf_next_play_active<MultiChannel>(unit, inNumSamples);

    float* trig = IN(0);
    for (int i = 0; i < inNumSamples; i++) {
        if ((trig[i] > 0) && (unit->curtrig <= 0))
            GrainBuf_next_start_new<true, MultiChannel>(unit, inNumSamples, i);
        unit->curtrig = trig[i];
    }
}

template <bool MultiChannel> static void GrainBuf_next_k(GrainBuf* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    GrainBuf_next_play_active<MultiChannel>(unit, inNumSamples);

    float trig = IN0(0);
    if ((trig > 0) && (unit->curtrig <= 0))
        GrainBuf_next_start_new<false, MultiChannel>(unit, inNumSamples, 0);
    unit->curtrig = trig;
}

FLATTEN static void GrainBuf_next_k_1(GrainBuf* unit, int inNumSamples) { GrainBuf_next_k<true>(unit, inNumSamples); }

FLATTEN static void GrainBuf_next_k_2(GrainBuf* unit, int inNumSamples) { GrainBuf_next_k<false>(unit, inNumSamples); }

FLATTEN static void GrainBuf_next_a_1(GrainBuf* unit, int inNumSamples) { GrainBuf_next_a<true>(unit, inNumSamples); }

FLATTEN static void GrainBuf_next_a_2(GrainBuf* unit, int inNumSamples) { GrainBuf_next_a<false>(unit, inNumSamples); }

void GrainBuf_Ctor(GrainBuf* unit) {
    unit->mNumActive = 0;
    unit->curtrig = 0.f;

    float maxGrains = IN0(8);
    unit->mMaxGrains = (int)maxGrains;
    unit->mGrains = (GrainBufG*)RTAlloc(unit->mWorld, unit->mMaxGrains * sizeof(GrainBufG));
    ClearUnitIfMemFailed(unit->mGrains);

    if (unit->mNumOutputs == 1) {
        if (INRATE(0) == calc_FullRate)
            SETCALC(GrainBuf_next_a_1);
        else
            SETCALC(GrainBuf_next_k_1);
    } else {
        if (INRATE(0) == calc_FullRate)
            SETCALC(GrainBuf_next_a_2);
        else
            SETCALC(GrainBuf_next_k_2);
    }

    (unit->mCalcFunc)(unit, 1);
}

void GrainBuf_Dtor(GrainBuf* unit) { RTFree(unit->mWorld, unit->mGrains); }


#define BUF_GRAIN_LOOP_BODY_4_N                                                                                        \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    const float* table1 = bufData + iphase * bufChannels;                                                              \
    const float* table0 = table1 - bufChannels;                                                                        \
    const float* table2 = table1 + bufChannels;                                                                        \
    const float* table3 = table2 + bufChannels;                                                                        \
    if (iphase == 0) {                                                                                                 \
        table0 += bufSamples;                                                                                          \
    } else if (iphase >= guardFrame) {                                                                                 \
        if (iphase == guardFrame) {                                                                                    \
            table3 -= bufSamples;                                                                                      \
        } else {                                                                                                       \
            table2 -= bufSamples;                                                                                      \
            table3 -= bufSamples;                                                                                      \
        }                                                                                                              \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float a = table0[n];                                                                                               \
    float b = table1[n];                                                                                               \
    float c = table2[n];                                                                                               \
    float d = table3[n];                                                                                               \
    float outval = amp * cubicinterp(fracphase, a, b, c, d);                                                           \
    ZXP(out1) += outval;

#define BUF_GRAIN_LOOP_BODY_2_N                                                                                        \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    const float* table1 = bufData + iphase * bufChannels;                                                              \
    const float* table2 = table1 + bufChannels;                                                                        \
    if (iphase > guardFrame) {                                                                                         \
        table2 -= bufSamples;                                                                                          \
    }                                                                                                                  \
    float fracphase = phase - (double)iphase;                                                                          \
    float b = table1[n];                                                                                               \
    float c = table2[n];                                                                                               \
    float outval = amp * (b + fracphase * (c - b));                                                                    \
    ZXP(out1) += outval;

// amp needs to be calculated by looking up values in window

#define BUF_GRAIN_LOOP_BODY_1_N                                                                                        \
    phase = sc_gloop(phase, loopMax);                                                                                  \
    int32 iphase = (int32)phase;                                                                                       \
    float outval = amp * bufData[iphase + n];                                                                          \
    ZXP(out1) += outval;


#define GET_WARP_WIN_RELAXED(WINTYPE)                                                                                  \
    do {                                                                                                               \
        assert(WINTYPE < unit->mWorld->mNumSndBufs);                                                                   \
        window = unit->mWorld->mSndBufs + (int)WINTYPE;                                                                \
        while (!TRY_ACQUIRE_SNDBUF_SHARED(window)) {                                                                   \
            RELEASE_SNDBUF_SHARED(buf);                                                                                \
            ACQUIRE_SNDBUF_SHARED(buf);                                                                                \
        }                                                                                                              \
        windowData = window->data;                                                                                     \
        if (windowData == NULL)                                                                                        \
            RELEASE_SNDBUF_SHARED(window);                                                                             \
        windowSamples = window->samples;                                                                               \
        windowFrames = window->frames;                                                                                 \
        windowGuardFrame = windowFrames - 1;                                                                           \
    } while (0);

#define GET_WARP_AMP_PARAMS                                                                                            \
    if (grain->winType < 0.) {                                                                                         \
        b1 = grain->b1;                                                                                                \
        y1 = grain->y1;                                                                                                \
        y2 = grain->y2;                                                                                                \
        amp = grain->curamp;                                                                                           \
    } else {                                                                                                           \
        GET_WARP_WIN_RELAXED(grain->winType);                                                                          \
        if (!windowData)                                                                                               \
            break;                                                                                                     \
        winPos = grain->winPos;                                                                                        \
        winInc = grain->winInc;                                                                                        \
        amp = grain->curamp;                                                                                           \
    }


void Warp1_next(Warp1* unit, int inNumSamples) {
    ClearUnitOutputs(unit, inNumSamples);

    GET_BUF
    SETUP_OUT
    CHECK_BUF

    for (uint32 n = 0; n < numOutputs; n++) {
        int nextGrain = unit->mNextGrain[n];
        for (int i = 0; i < unit->mNumActive[n];) {
            WarpWinGrain* grain = unit->mGrains[n] + i;

            double loopMax = (double)bufFrames;

            double rate = grain->rate;
            double phase = grain->phase;
            DECLARE_WINDOW
            GET_GRAIN_AMP_PARAMS
            float* out1 = out[n];
            int nsmps = sc_min(grain->counter, inNumSamples);
            if (grain->interp >= 4) {
                for (int j = 0; j < nsmps; ++j) {
                    BUF_GRAIN_LOOP_BODY_4_N
                    CALC_NEXT_GRAIN_AMP
                    phase += rate;
                }
            } else if (grain->interp >= 2) {
                for (int j = 0; j < nsmps; ++j) {
                    BUF_GRAIN_LOOP_BODY_2_N
                    CALC_NEXT_GRAIN_AMP
                    phase += rate;
                }
            } else {
                for (int j = 0; j < nsmps; ++j) {
                    BUF_GRAIN_LOOP_BODY_1_N
                    CALC_NEXT_GRAIN_AMP
                    phase += rate;
                }
            }

            grain->phase = phase;
            SAVE_GRAIN_AMP_PARAMS
            if (grain->counter <= 0)
                *grain = unit->mGrains[n][--unit->mNumActive[n]]; // remove grain
            else
                ++i;
        }

        for (int i = 0; i < inNumSamples; ++i) {
            --nextGrain;
            if (nextGrain == 0) {
                // start a grain
                if (unit->mNumActive[n] + 1 >= kMaxGrains)
                    break;
                //			uint32 bufnum = (uint32)GRAIN_IN_AT(unit, 0, i);
                //			if (bufnum >= numBufs) break;

                float bufSampleRate = buf->samplerate;
                float bufRateScale = bufSampleRate * SAMPLEDUR;
                double loopMax = (double)bufFrames;

                WarpWinGrain* grain = unit->mGrains[n] + unit->mNumActive[n]++;
                //			grain->bufnum = bufnum;

                float overlaps = GRAIN_IN_AT(unit, 5, i);
                float counter = GRAIN_IN_AT(unit, 3, i) * SAMPLERATE;
                double winrandamt = unit->mParent->mRGen->frand2() * (double)GRAIN_IN_AT(unit, 6, i);
                counter = sc_max(4., floor(counter + (counter * winrandamt)));
                grain->counter = (int)counter;

                nextGrain = (int)(counter / overlaps);

                unit->mNextGrain[n] = nextGrain;

                float rate = grain->rate = GRAIN_IN_AT(unit, 2, i) * bufRateScale;
                float phase = GRAIN_IN_AT(unit, 1, i) * (float)bufFrames;
                grain->interp = (int)GRAIN_IN_AT(unit, 7, i);
                float winType = grain->winType = (int)GRAIN_IN_AT(unit, 4, i); // the buffer that holds the grain shape
                DECLARE_WINDOW

                if (winType >= unit->mWorld->mNumSndBufs) {
                    Print("Envelope buffer out of range!\n");
                    break;
                }

                GET_GRAIN_WIN_RELAXED(winType)
                if (windowData || (winType < 0.)) {
                    GET_GRAIN_INIT_AMP

                    float* out1 = out[n] + i;

                    int nsmps = sc_min(grain->counter, inNumSamples - i);
                    if (grain->interp >= 4) {
                        for (int j = 0; j < nsmps; ++j) {
                            BUF_GRAIN_LOOP_BODY_4_N
                            CALC_NEXT_GRAIN_AMP
                            phase += rate;
                        }
                    } else if (grain->interp >= 2) {
                        for (int j = 0; j < nsmps; ++j) {
                            BUF_GRAIN_LOOP_BODY_2_N
                            CALC_NEXT_GRAIN_AMP
                            phase += rate;
                        }
                    } else {
                        for (int j = 0; j < nsmps; ++j) {
                            BUF_GRAIN_LOOP_BODY_1_N
                            CALC_NEXT_GRAIN_AMP
                            phase += rate;
                        }
                    }

                    grain->phase = phase;
                    SAVE_GRAIN_AMP_PARAMS
                    // end change
                    if (grain->counter <= 0)
                        *grain = unit->mGrains[n][--unit->mNumActive[n]]; // remove grain
                }
            }
        }

        unit->mNextGrain[n] = nextGrain;
    }
}

void Warp1_Ctor(Warp1* unit) {
    SETCALC(Warp1_next);

    for (int i = 0; i < 16; i++) {
        unit->mNumActive[i] = 0;
        unit->mNextGrain[i] = 1;
    }

    ClearUnitOutputs(unit, 1);
    unit->m_fbufnum = -1e9f;
}


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

PluginLoad(Grain) {
    ft = inTable;

    DefineDtorCantAliasUnit(GrainIn);
    DefineDtorCantAliasUnit(GrainSin);
    DefineDtorCantAliasUnit(GrainFM);
    DefineDtorCantAliasUnit(GrainBuf);
    DefineSimpleCantAliasUnit(Warp1);
}
