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

// Convolution by nick collins for sc3 (later input from Marije as below, Dan Stowell and Nick worked on sc_fft
// compatibility late 2008) see ch18 http://www.dspguide.com/ch18.htm Steven W Smith

// Convolution2 adapted by marije baalman for triggered kernel swap, with help from alberto de campo
// Convolution2L (with linear crossfade) by marije baalman
// Convolution3 (time-based) by marije baalman

#include "FFT_UGens.h"

//#include "SC_fftlib.h"

#include <stdio.h>


struct Convolution : Unit {
    int m_pos, m_framesize, m_fftsize;
    float *m_inbuf1, *m_inbuf2, *m_fftbuf1, *m_fftbuf2, *m_outbuf, *m_overlapbuf;
    scfft *m_scfft1, *m_scfft2, *m_scfftR;
};


struct Convolution2 : Unit {
    int m_pos, m_framesize, m_fftsize;
    float m_prevtrig;
    float *m_inbuf1, *m_fftbuf1, *m_fftbuf2, *m_outbuf, *m_overlapbuf;
    scfft *m_scfft1, *m_scfft2, *m_scfftR;
};

struct Convolution2L : Unit {
    int m_pos, m_framesize, m_fftsize;
    int m_cfpos, m_cflength, m_curbuf; // for crossfading
    int m_log2n;
    float m_prevtrig;
    float *m_inbuf1, *m_fftbuf1, *m_fftbuf2, *m_outbuf, *m_overlapbuf;
    float *m_tempbuf, *m_fftbuf3; // for crossfading

    scfft *m_scfft1, *m_scfft2, *m_scfft3, *m_scfftR,
        *m_scfftR2; // source plus two kernels forwards, and two inverse from outbuf and from tempbuf
};


// could be done also using complex signal fft and appropriate unwrapping, but sc_fft doesn't support that
struct StereoConvolution2L : Unit {
    int m_pos, m_framesize, m_fftsize;
    int m_cfpos, m_cflength, m_curbuf; // for crossfading
    int m_log2n;
    float m_prevtrig;
    float *m_inbuf1, *m_fftbuf1; // input
    float *m_fftbuf2[2], *m_outbuf[2], *m_overlapbuf[2]; // output
    float *m_tempbuf[2], *m_fftbuf3[2]; // for crossfading

    scfft *m_scfft1, *m_scfft2[2], *m_scfft3[2], *m_scfftR[2], *m_scfftR2[2];
};

struct Convolution3 : Unit {
    int m_pos, m_framesize;
    float m_prevtrig;
    float *m_inbuf1, *m_inbuf2, *m_outbuf;
};

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

extern "C" {
void Convolution_next(Convolution* unit, int wrongNumSamples);
void Convolution_Ctor(Convolution* unit);
void Convolution_Dtor(Convolution* unit);

void Convolution2_next(Convolution2* unit, int wrongNumSamples);
void Convolution2_Ctor(Convolution2* unit);
void Convolution2_Dtor(Convolution2* unit);

void Convolution2L_next(Convolution2L* unit, int wrongNumSamples);
void Convolution2L_Ctor(Convolution2L* unit);
void Convolution2L_Dtor(Convolution2L* unit);

void StereoConvolution2L_next(StereoConvolution2L* unit, int wrongNumSamples);
void StereoConvolution2L_Ctor(StereoConvolution2L* unit);
void StereoConvolution2L_Dtor(StereoConvolution2L* unit);

void Convolution3_next_a(Convolution3* unit);
void Convolution3_next_k(Convolution3* unit);
void Convolution3_Ctor(Convolution3* unit);
void Convolution3_Dtor(Convolution3* unit);
}


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

// PROPER CONVOLVER
// two possibilities- fixed kernel (in which case can derive the kernel spectrum in the constructor)
// and changing kernel (same size as target)

void Convolution_Ctor(Convolution* unit) {
    // require size N+M-1 to be a power of two
    unit->m_framesize = (int)ZIN0(2);

    unit->m_fftsize = 2 * (unit->m_framesize);
    // just use memory for the input buffers and fft buffers
    int framesize_f = unit->m_framesize * sizeof(float);
    int fftsize_f = unit->m_fftsize * sizeof(float);

    unit->m_scfft1 = unit->m_scfft2 = unit->m_scfftR = nullptr;

    unit->m_inbuf1 = (float*)RTAlloc(unit->mWorld, framesize_f);
    unit->m_inbuf2 = (float*)RTAlloc(unit->mWorld, framesize_f);
    unit->m_fftbuf1 = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf2 = (float*)RTAlloc(unit->mWorld, fftsize_f);

    unit->m_outbuf = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_overlapbuf = (float*)RTAlloc(unit->mWorld, framesize_f);

    ClearUnitIfMemFailed(unit->m_inbuf1 && unit->m_inbuf2 && unit->m_fftbuf1 && unit->m_fftbuf2 && unit->m_outbuf
                         && unit->m_overlapbuf);

    memset(unit->m_outbuf, 0, fftsize_f);
    memset(unit->m_overlapbuf, 0, framesize_f);

    unit->m_pos = 0;

    SCWorld_Allocator alloc(ft, unit->mWorld);
    unit->m_scfft1 =
        scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_fftbuf1, kForward, alloc);
    unit->m_scfft2 =
        scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf2, unit->m_fftbuf2, kForward, alloc);
    unit->m_scfftR =
        scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_outbuf, kBackward, alloc);
    ClearUnitIfMemFailed(unit->m_scfft1 && unit->m_scfft2 && unit->m_scfftR);
    SETCALC(Convolution_next);

    // initialize output
    OUT0(0) = IN0(0);
}


void Convolution_Dtor(Convolution* unit) {
    RTFree(unit->mWorld, unit->m_inbuf1);
    RTFree(unit->mWorld, unit->m_inbuf2);
    RTFree(unit->mWorld, unit->m_fftbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf2);
    RTFree(unit->mWorld, unit->m_outbuf);
    RTFree(unit->mWorld, unit->m_overlapbuf);
    SCWorld_Allocator alloc(ft, unit->mWorld);
    scfft_destroy(unit->m_scfft1, alloc);
    scfft_destroy(unit->m_scfft2, alloc);
    scfft_destroy(unit->m_scfftR, alloc);
}


void Convolution_next(Convolution* unit, int numSamples) {
    float* in1 = IN(0);
    float* in2 = IN(1);

    float* out1 = unit->m_inbuf1 + unit->m_pos;
    float* out2 = unit->m_inbuf2 + unit->m_pos;

    // int numSamples = unit->mWorld->mFullRate.mBufLength;

    // copy input
    Copy(numSamples, out1, in1);
    Copy(numSamples, out2, in2);

    unit->m_pos += numSamples;

    int framesize = unit->m_framesize;

    if (unit->m_pos & framesize) {
        // have collected enough samples to transform next frame
        unit->m_pos = 0; // reset collection counter

        int memsize = framesize * sizeof(float);

        // copy to fftbuf
        memcpy(unit->m_fftbuf1, unit->m_inbuf1, memsize);
        memcpy(unit->m_fftbuf2, unit->m_inbuf2, memsize);

        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf1 + unit->m_framesize, 0, memsize);
        memset(unit->m_fftbuf2 + unit->m_framesize, 0, memsize);

        // do fft
        // in place transform for now
        scfft_dofft(unit->m_scfft1);
        scfft_dofft(unit->m_scfft2);

        // complex multiply time
        float* p1 = unit->m_fftbuf1;
        float* p2 = unit->m_fftbuf2;

        p1[0] *= p2[0];
        p1[1] *= p2[1];

        // complex multiply
        for (int i = 1; i < framesize; ++i) {
            float real, imag;
            int realind, imagind;
            realind = 2 * i;
            imagind = realind + 1;
            real = p1[realind] * p2[realind] - p1[imagind] * p2[imagind];
            imag = p1[realind] * p2[imagind] + p1[imagind] * p2[realind];

            p1[realind] = real;
            p1[imagind] = imag;
        }

        // copy second part from before to overlap
        memcpy(unit->m_overlapbuf, unit->m_outbuf + unit->m_framesize, memsize);

        // inverse fft into outbuf
        memcpy(unit->m_outbuf, unit->m_fftbuf1, unit->m_fftsize * sizeof(float));

        // in place
        scfft_doifft(unit->m_scfftR);
    }

    // write out samples copied from outbuf, with overlap added in

    float* output = ZOUT(0);
    float* out = unit->m_outbuf + unit->m_pos;
    float* overlap = unit->m_overlapbuf + unit->m_pos;

    for (int i = 0; i < numSamples; ++i)
        ZXP(output) = out[i] + overlap[i];
}


// include local buffer test in one place
static SndBuf* ConvGetBuffer(Unit* unit, uint32 bufnum, const char* ugenName, int inNumSamples) {
    SndBuf* buf;
    World* world = unit->mWorld;

    if (bufnum >= world->mNumSndBufs) {
        int localBufNum = bufnum - world->mNumSndBufs;
        Graph* parent = unit->mParent;
        if (localBufNum <= parent->localMaxBufNum) {
            buf = parent->mLocalSndBufs + localBufNum;
        } else {
            if (unit->mWorld->mVerbosity > -1)
                Print("%s: invalid buffer number (%d).\n", ugenName, bufnum);
            goto handle_failure;
        }
    } else {
        buf = world->mSndBufs + bufnum;
    }

    if (buf->data == nullptr) {
        if (unit->mWorld->mVerbosity > -1)
            Print("%s: uninitialized buffer (%i).\n", ugenName, bufnum);
        goto handle_failure;
    }

    return buf;

handle_failure:
    SETCALC(*ClearUnitOutputs);
    ClearUnitOutputs(unit, inNumSamples);
    unit->mDone = true;
    return nullptr;
}

void Convolution2_Ctor(Convolution2* unit) {
    // require size N+M-1 to be a power of two
    unit->m_framesize = (int)ZIN0(3);
    uint32 kernelbufnum = (int)ZIN0(1);
    World* world = unit->mWorld;

    unit->m_inbuf1 = unit->m_fftbuf1 = unit->m_fftbuf2 = unit->m_outbuf = unit->m_overlapbuf = nullptr;
    unit->m_scfft1 = unit->m_scfft2 = unit->m_scfftR = nullptr;
    SndBuf* kernelbuf = ConvGetBuffer(unit, kernelbufnum, "Convolution2", 1);

    if (kernelbuf) {
        if (unit->m_framesize <= 0) { // if smaller than zero, we use the size of the buffer
            unit->m_framesize = kernelbuf->frames;
        }

        unit->m_fftsize = 2 * (unit->m_framesize);

        if (unit->m_fftsize > SC_FFT_ABSOLUTE_MAXSIZE) {
            printf("Convolution2: FFT size is larger than SC_FFT_ABSOLUTE_MAXSIZE, cannot run. We suggest PartConv "
                   "instead.\n");
            SETCALC(*ClearUnitOutputs);
        }

        // allocate memory internally for the input buffers and fft buffers
        int framesize_f = unit->m_framesize * sizeof(float);
        int fftsize_f = unit->m_fftsize * sizeof(float);
        unit->m_inbuf1 = (float*)RTAlloc(world, framesize_f);
        unit->m_fftbuf1 = (float*)RTAlloc(world, fftsize_f);
        unit->m_fftbuf2 = (float*)RTAlloc(world, fftsize_f);

        unit->m_outbuf = (float*)RTAlloc(world, fftsize_f);
        unit->m_overlapbuf = (float*)RTAlloc(world, framesize_f);
        ClearUnitIfMemFailed(unit->m_inbuf1 && unit->m_fftbuf1 && unit->m_fftbuf2 && unit->m_outbuf
                             && unit->m_overlapbuf);

        memset(unit->m_outbuf, 0, fftsize_f);
        memset(unit->m_overlapbuf, 0, framesize_f);
        unit->m_pos = 0;

        SCWorld_Allocator alloc(ft, unit->mWorld);
        unit->m_scfft1 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_fftbuf1,
                                      kForward, alloc);
        unit->m_scfft2 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf2, unit->m_fftbuf2,
                                      kForward, alloc);
        unit->m_scfftR = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_outbuf,
                                      kBackward, alloc);
        ClearUnitIfMemFailed(unit->m_scfft1 && unit->m_scfft2 && unit->m_scfftR);

        // calculate fft for kernel straight away
        // we cannot use a kernel larger than the fft size, so truncate if needed. the kernel may be smaller though.
        uint32 framesize = unit->m_framesize;
        size_t kernelcopysize = sc_min(kernelbuf->frames, framesize);
        memcpy(unit->m_fftbuf2, kernelbuf->data, kernelcopysize * sizeof(float));
        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf2 + kernelcopysize, 0, (2 * framesize - kernelcopysize) * sizeof(float));

        scfft_dofft(unit->m_scfft2);

        unit->m_pos = 0;

        unit->m_prevtrig = 0.f;

        if (unit->m_framesize >= world->mFullRate.mBufLength) {
            SETCALC(Convolution2_next);

            // initialize output
            OUT0(0) = IN0(0);
        } else {
            printf("Convolution2 framesize smaller than blocksize \n");
            SETCALC(*ClearUnitOutputs);
            unit->mDone = true;
        }
    } else {
        unit->m_scfft2 = unit->m_scfft1 = unit->m_scfftR = nullptr;
        printf("Convolution2_Ctor: can't get kernel buffer, giving up.\n");
        SETCALC(*ClearUnitOutputs);
    }
}

void Convolution2_Dtor(Convolution2* unit) {
    RTFree(unit->mWorld, unit->m_inbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf2);
    RTFree(unit->mWorld, unit->m_outbuf);
    RTFree(unit->mWorld, unit->m_overlapbuf);

    SCWorld_Allocator alloc(ft, unit->mWorld);
    scfft_destroy(unit->m_scfft1, alloc);
    scfft_destroy(unit->m_scfft2, alloc);
    scfft_destroy(unit->m_scfftR, alloc);
}


void Convolution2_next(Convolution2* unit, int wrongNumSamples) {
    float* in1 = IN(0);
    float curtrig = ZIN0(2);

    float* inbuf1writepos = unit->m_inbuf1 + unit->m_pos;

    int numSamples = unit->mWorld->mFullRate.mBufLength;
    uint32 framesize = unit->m_framesize;
    uint32 framesize_f = framesize * sizeof(float);

    // copy input
    Copy(numSamples, inbuf1writepos, in1);

    unit->m_pos += numSamples;

    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        SndBuf* kernelbuf = ConvGetBuffer(unit, (uint32)ZIN0(1), "Convolution2", numSamples);
        if (!kernelbuf)
            return;
        LOCK_SNDBUF_SHARED(kernelbuf);

        // we cannot use a kernel larger than the fft size, so truncate if needed. the kernel may be smaller though.
        size_t kernelcopysize = sc_min(kernelbuf->frames, framesize);
        memcpy(unit->m_fftbuf2, kernelbuf->data, kernelcopysize * sizeof(float));
        memset(unit->m_fftbuf2 + kernelcopysize, 0, (2 * framesize - kernelcopysize) * sizeof(float));

        scfft_dofft(unit->m_scfft2);
    }

    if (unit->m_pos >= framesize) {
        // have collected enough samples to transform next frame
        unit->m_pos = 0; // reset collection counter

        // copy to fftbuf
        memcpy(unit->m_fftbuf1, unit->m_inbuf1, framesize_f);

        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf1 + unit->m_framesize, 0, framesize_f);

        scfft_dofft(unit->m_scfft1);

        // complex multiply time
        int numbins = unit->m_fftsize >> 1;

        float* p1 = unit->m_fftbuf1;
        float* p2 = unit->m_fftbuf2;

        p1[0] *= p2[0];
        p1[1] *= p2[1];

        // complex multiply
        for (int i = 1; i < numbins; ++i) {
            float real, imag;
            int realind, imagind;
            realind = 2 * i;
            imagind = realind + 1;
            real = p1[realind] * p2[realind] - p1[imagind] * p2[imagind];
            imag = p1[realind] * p2[imagind] + p1[imagind] * p2[realind];
            p1[realind] = real;
            p1[imagind] = imag;
        }

        // copy second part from before to overlap
        memcpy(unit->m_overlapbuf, unit->m_outbuf + unit->m_framesize, framesize_f);
        // inverse fft into outbuf
        scfft_doifft(unit->m_scfftR);
    }

    // write out samples copied from outbuf, with overlap added in

    float* output = ZOUT(0);
    float* out = unit->m_outbuf + unit->m_pos;
    float* overlap = unit->m_overlapbuf + unit->m_pos;
    unit->m_prevtrig = curtrig;

    for (int i = 0; i < numSamples; ++i)
        ZXP(output) = out[i] + overlap[i];
}

void Convolution2L_Ctor(Convolution2L* unit) {
    // require size N+M-1 to be a power of two
    unit->m_framesize = (int)ZIN0(3);
    unit->m_cflength = (int)ZIN0(4);
    unit->m_curbuf = 0;
    unit->m_cfpos = unit->m_cflength;


    unit->m_fftsize = 2 * (unit->m_framesize);
    // just use memory for the input buffers and fft buffers
    int framesize_f = unit->m_framesize * sizeof(float);
    int fftsize_f = unit->m_fftsize * sizeof(float);

    unit->m_inbuf1 = (float*)RTAlloc(unit->mWorld, framesize_f);

    unit->m_fftbuf1 = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf2 = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf3 = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_tempbuf = (float*)RTAlloc(unit->mWorld, fftsize_f);

    unit->m_outbuf = unit->m_overlapbuf = nullptr;
    unit->m_scfft1 = unit->m_scfft2 = unit->m_scfft3 = nullptr;
    unit->m_scfftR = unit->m_scfftR2 = nullptr;

    ClearUnitIfMemFailed(unit->m_inbuf1 && unit->m_fftbuf1 && unit->m_fftbuf2 && unit->m_fftbuf3 && unit->m_tempbuf);

    uint32 bufnum = (int)ZIN0(1); // fbufnum;

    SndBuf* buf = ConvGetBuffer(unit, bufnum, "Convolution2L", 1);

    if (buf) {
        unit->m_outbuf = (float*)RTAlloc(unit->mWorld, fftsize_f);
        unit->m_overlapbuf = (float*)RTAlloc(unit->mWorld, framesize_f);

        ClearUnitIfMemFailed(unit->m_outbuf && unit->m_overlapbuf);

        memset(unit->m_outbuf, 0, fftsize_f);
        memset(unit->m_overlapbuf, 0, framesize_f);

        // calculate fft for kernel straight away
        memcpy(unit->m_fftbuf2, buf->data, framesize_f);
        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf2 + unit->m_framesize, 0, framesize_f);

        SCWorld_Allocator alloc(ft, unit->mWorld);
        unit->m_scfft1 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_fftbuf1,
                                      kForward, alloc);
        unit->m_scfft2 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf2, unit->m_fftbuf2,
                                      kForward, alloc);
        unit->m_scfft3 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf3, unit->m_fftbuf3,
                                      kForward, alloc);
        unit->m_scfftR = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_outbuf, unit->m_outbuf,
                                      kBackward, alloc);
        unit->m_scfftR2 = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_tempbuf, unit->m_tempbuf,
                                       kBackward, alloc);

        ClearUnitIfMemFailed(unit->m_scfft1 && unit->m_scfft2 && unit->m_scfft3 && unit->m_scfftR && unit->m_scfftR2);
        scfft_dofft(unit->m_scfft2);

        unit->m_pos = 0;
        unit->m_prevtrig = 0.f;

        SETCALC(Convolution2L_next);

        // initialize output
        OUT0(0) = IN0(0);
    } else {
        unit->m_scfft1 = unit->m_scfft2 = unit->m_scfft3 = unit->m_scfftR = unit->m_scfftR2 = nullptr;
    }
}

void Convolution2L_Dtor(Convolution2L* unit) {
    SCWorld_Allocator alloc(ft, unit->mWorld);
    scfft_destroy(unit->m_scfft1, alloc);
    scfft_destroy(unit->m_scfft2, alloc);
    scfft_destroy(unit->m_scfft3, alloc);
    scfft_destroy(unit->m_scfftR, alloc);
    scfft_destroy(unit->m_scfftR2, alloc);


    RTFree(unit->mWorld, unit->m_inbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf2);
    RTFree(unit->mWorld, unit->m_fftbuf3);
    RTFree(unit->mWorld, unit->m_tempbuf);
    RTFree(unit->mWorld, unit->m_outbuf);
    RTFree(unit->mWorld, unit->m_overlapbuf);
}


void Convolution2L_next(Convolution2L* unit, int numSamples) {
    float* in1 = IN(0);
    float curtrig = ZIN0(2);
    float* out1 = unit->m_inbuf1 + unit->m_pos;
    int framesize_f = unit->m_framesize * sizeof(float);

    // copy input
    memcpy(out1, in1, numSamples * sizeof(float));

    unit->m_pos += numSamples;

    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        uint32 bufnum = (int)ZIN0(1);
        SndBuf* buf = ConvGetBuffer(unit, bufnum, "Convolution2L", numSamples);
        if (!buf)
            return;

        unit->m_cflength = (int)ZIN0(4);

        unit->m_cfpos = 0;
        if (unit->m_curbuf == 1) {
            LOCK_SNDBUF_SHARED(buf);
            memcpy(unit->m_fftbuf2, buf->data, framesize_f);
            memset(unit->m_fftbuf2 + unit->m_framesize, 0, framesize_f);

            scfft_dofft(unit->m_scfft2);
        } else if (unit->m_curbuf == 0) {
            LOCK_SNDBUF_SHARED(buf);
            memcpy(unit->m_fftbuf3, buf->data, framesize_f);
            memset(unit->m_fftbuf3 + unit->m_framesize, 0, framesize_f);

            scfft_dofft(unit->m_scfft3);
        }
    }

    if (unit->m_pos & unit->m_framesize) {
        // have collected enough samples to transform next frame
        unit->m_pos = 0; // reset collection counter

        // copy to fftbuf
        memcpy(unit->m_fftbuf1, unit->m_inbuf1, framesize_f);

        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf1 + unit->m_framesize, 0, framesize_f);
        // in place transform for now
        scfft_dofft(unit->m_scfft1);

        // rffts(unit->m_fftbuf1, log2n, 1, cosTable[log2n]);
        // complex multiply time
        int numbins = unit->m_fftsize >> 1; // unit->m_fftsize - 2 >> 1;

        float* p1 = unit->m_fftbuf1;
        float* p2;
        if (unit->m_curbuf == 0)
            p2 = unit->m_fftbuf2;
        else
            p2 = unit->m_fftbuf3;
        float* p3 = unit->m_tempbuf;

        // multiply the dc and nyquist components
        p3[0] = p1[0] * p2[0];
        p3[1] = p1[1] * p2[1];
        // complex multiply
        for (int i = 1; i < numbins; ++i) {
            float real, imag;
            int realind, imagind;
            realind = 2 * i;
            imagind = realind + 1;
            real = p1[realind] * p2[realind] - p1[imagind] * p2[imagind];
            imag = p1[realind] * p2[imagind] + p1[imagind] * p2[realind];
            p3[realind] = real;
            p3[imagind] = imag;
        }

        // copy second part from before to overlap
        memcpy(unit->m_overlapbuf, unit->m_outbuf + unit->m_framesize, unit->m_framesize * sizeof(float));
        // inverse fft into outbuf
        memcpy(unit->m_outbuf, unit->m_tempbuf, unit->m_fftsize * sizeof(float));
        // in place
        scfft_doifft(unit->m_scfftR);

        if (unit->m_cfpos < unit->m_cflength) // do crossfade
        {
            if (unit->m_curbuf == 0)
                p2 = unit->m_fftbuf3;
            else
                p2 = unit->m_fftbuf2;

            // multiply the dc and nyquist components
            p1[0] *= p2[0];
            p1[1] *= p2[1];

            // complex multiply
            for (int i = 1; i < numbins; ++i) {
                float real, imag;
                int realind, imagind;
                realind = 2 * i;
                imagind = realind + 1;
                real = p1[realind] * p2[realind] - p1[imagind] * p2[imagind];
                imag = p1[realind] * p2[imagind] + p1[imagind] * p2[realind];
                p1[realind] = real; // p2->bin[i];
                p1[imagind] = imag;
            }

            // copy second part from before to overlap
            // inverse fft into tempbuf
            memcpy(unit->m_tempbuf, unit->m_fftbuf1, unit->m_fftsize * sizeof(float));
            scfft_doifft(unit->m_scfftR2);

            // now crossfade between outbuf and tempbuf
            float fact1 = (float)unit->m_cfpos / unit->m_cflength; // crossfade amount startpoint
            float rc = 1.f / (unit->m_cflength * unit->m_framesize); // crossfade amount increase per sample
            float* p4 = unit->m_outbuf;
            float* p5 = unit->m_tempbuf;
            for (int i = 0; i < unit->m_framesize; i++) {
                float res;
                res = (1 - fact1) * p4[i] + fact1 * p5[i];
                fact1 += rc;
                p4[i] = res;
            }
            if (unit->m_cflength == 1)
                memcpy(unit->m_outbuf + unit->m_framesize, unit->m_tempbuf + unit->m_framesize, framesize_f);
            else {
                for (int i = unit->m_framesize + 1; i < unit->m_fftsize; i++) {
                    float res = (1 - fact1) * p4[i] + fact1 * p5[i];
                    fact1 += rc;
                    p4[i] = res;
                }
            }
            unit->m_cfpos++;
            if (unit->m_cfpos == unit->m_cflength) // at end of crossfade, update the current buffer index
            {
                if (unit->m_curbuf == 0)
                    unit->m_curbuf = 1;
                else
                    unit->m_curbuf = 0;
            }
        }
    }

    // write out samples copied from outbuf, with overlap added in
    float* output = ZOUT(0);
    float* out = unit->m_outbuf + unit->m_pos;
    float* overlap = unit->m_overlapbuf + unit->m_pos;
    unit->m_prevtrig = curtrig;

    for (int i = 0; i < numSamples; ++i)
        ZXP(output) = out[i] + overlap[i];
}

/** basically the same as Convolution2L, but takes a stereo buffer to convolve with and outputs a stereo signal */
void StereoConvolution2L_Ctor(StereoConvolution2L* unit) {
    // require size N+M-1 to be a power of two
    unit->m_framesize = (int)ZIN0(4);
    unit->m_cflength = (int)ZIN0(5);
    unit->m_curbuf = 0;
    unit->m_cfpos = unit->m_cflength;

    unit->m_fftsize = 2 * (unit->m_framesize);
    // just use memory for the input buffers and fft buffers
    int framesize_f = unit->m_framesize * sizeof(float);
    int fftsize_f = unit->m_fftsize * sizeof(float);

    unit->m_inbuf1 = (float*)RTAlloc(unit->mWorld, framesize_f);

    // source:
    unit->m_fftbuf1 = (float*)RTAlloc(unit->mWorld, fftsize_f);

    // 2 channel buffer:
    unit->m_fftbuf2[0] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf2[1] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf3[0] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_fftbuf3[1] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_tempbuf[0] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_tempbuf[1] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    // 		unit->m_tempfftbuf[0] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    // 		unit->m_tempfftbuf[1] = (float*)RTAlloc(unit->mWorld, fftsize_f);

    unit->m_outbuf[0] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_overlapbuf[0] = (float*)RTAlloc(unit->mWorld, framesize_f);
    unit->m_outbuf[1] = (float*)RTAlloc(unit->mWorld, fftsize_f);
    unit->m_overlapbuf[1] = (float*)RTAlloc(unit->mWorld, framesize_f);
    unit->m_scfft1 = nullptr;
    unit->m_scfft2[0] = unit->m_scfft3[0] = unit->m_scfftR[0] = unit->m_scfftR2[0] = nullptr;
    unit->m_scfft2[1] = unit->m_scfft3[1] = unit->m_scfftR[1] = unit->m_scfftR2[1] = nullptr;
    ClearUnitIfMemFailed(unit->m_inbuf1 && unit->m_fftbuf1 && unit->m_fftbuf2[0] && unit->m_fftbuf2[1]
                         && unit->m_fftbuf3[0] && unit->m_fftbuf3[1] && unit->m_tempbuf[0] && unit->m_tempbuf[1]
                         && unit->m_outbuf[0] && unit->m_outbuf[1] && unit->m_overlapbuf[0] && unit->m_overlapbuf[1]);

    memset(unit->m_outbuf[0], 0, fftsize_f);
    memset(unit->m_overlapbuf[0], 0, framesize_f);
    memset(unit->m_outbuf[1], 0, fftsize_f);
    memset(unit->m_overlapbuf[1], 0, framesize_f);

    SCWorld_Allocator alloc(ft, unit->mWorld);
    unit->m_scfft1 =
        scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf1, unit->m_fftbuf1, kForward, alloc);
    unit->m_scfft2[0] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf2[0],
                                     unit->m_fftbuf2[0], kForward, alloc);
    unit->m_scfft3[0] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf2[1],
                                     unit->m_fftbuf2[1], kForward, alloc);
    unit->m_scfftR[0] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf3[0],
                                     unit->m_fftbuf3[0], kForward, alloc);
    unit->m_scfftR2[0] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_fftbuf3[1],
                                      unit->m_fftbuf3[1], kForward, alloc);
    unit->m_scfft2[1] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_outbuf[0],
                                     unit->m_outbuf[0], kBackward, alloc);
    unit->m_scfft3[1] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_tempbuf[0],
                                     unit->m_tempbuf[0], kBackward, alloc);
    unit->m_scfftR[1] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_outbuf[1],
                                     unit->m_outbuf[1], kBackward, alloc);
    unit->m_scfftR2[1] = scfft_create(unit->m_fftsize, unit->m_fftsize, kRectWindow, unit->m_tempbuf[1],
                                      unit->m_tempbuf[1], kBackward, alloc);

    ClearUnitIfMemFailed(unit->m_scfft1 && unit->m_scfft2[0] && unit->m_scfft3[0] && unit->m_scfftR[0]
                         && unit->m_scfftR2[0]);
    ClearUnitIfMemFailed(unit->m_scfft2[1] && unit->m_scfft3[1] && unit->m_scfftR[1] && unit->m_scfftR2[1]);
    float fbufnum = ZIN0(1);
    uint32 bufnumL = (int)fbufnum;
    fbufnum = ZIN0(2);
    uint32 bufnumR = (int)fbufnum;
    // printf("bufnum %i \n", bufnum);

    // unit->m_log2n = LOG2CEIL(unit->m_fftsize);
    // int log2n = unit->m_log2n;

    SndBuf* buf = ConvGetBuffer(unit, bufnumL, "StereoConvolution2L", 1);

    if (buf) {
        LOCK_SNDBUF_SHARED(buf);

        // calculate fft for kernel straight away
        memcpy(unit->m_fftbuf2[0], buf->data, framesize_f);
        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf2[0] + unit->m_framesize, 0, framesize_f);
        // in place transform for now
        scfft_dofft(unit->m_scfft2[0]);
    } else
        return;

    buf = ConvGetBuffer(unit, bufnumR, "StereoConvolution2L", 1);
    if (buf) {
        LOCK_SNDBUF(buf);

        // calculate fft for kernel straight away
        memcpy(unit->m_fftbuf2[1], buf->data, framesize_f);
        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf2[1] + unit->m_framesize, 0, framesize_f);
        // in place transform for now
        scfft_dofft(unit->m_scfft2[1]);
        unit->m_pos = 0;
        unit->m_prevtrig = 0.f;

        SETCALC(StereoConvolution2L_next);

        // initialize outputs
        OUT0(0) = IN0(0);
        OUT0(1) = IN0(0);
    }
}

void StereoConvolution2L_Dtor(StereoConvolution2L* unit) {
    SCWorld_Allocator alloc(ft, unit->mWorld);
    scfft_destroy(unit->m_scfft1, alloc);
    scfft_destroy(unit->m_scfft2[0], alloc);
    scfft_destroy(unit->m_scfft3[0], alloc);
    scfft_destroy(unit->m_scfftR[0], alloc);
    scfft_destroy(unit->m_scfftR2[0], alloc);
    scfft_destroy(unit->m_scfft2[1], alloc);
    scfft_destroy(unit->m_scfft3[1], alloc);
    scfft_destroy(unit->m_scfftR[1], alloc);
    scfft_destroy(unit->m_scfftR2[1], alloc);

    RTFree(unit->mWorld, unit->m_inbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf1);
    RTFree(unit->mWorld, unit->m_fftbuf2[0]);
    RTFree(unit->mWorld, unit->m_fftbuf3[0]);
    RTFree(unit->mWorld, unit->m_tempbuf[0]);
    RTFree(unit->mWorld, unit->m_outbuf[0]);
    RTFree(unit->mWorld, unit->m_overlapbuf[0]);
    RTFree(unit->mWorld, unit->m_fftbuf2[1]);
    RTFree(unit->mWorld, unit->m_fftbuf3[1]);
    RTFree(unit->mWorld, unit->m_tempbuf[1]);
    RTFree(unit->mWorld, unit->m_outbuf[1]);
    RTFree(unit->mWorld, unit->m_overlapbuf[1]);
}


void StereoConvolution2L_next(StereoConvolution2L* unit, int wrongNumSamples) {
    float* in1 = IN(0);
    float curtrig = ZIN0(3);

    float* out1 = unit->m_inbuf1 + unit->m_pos;

    int numSamples = unit->mWorld->mFullRate.mBufLength;
    uint32 framesize_f = unit->m_framesize * sizeof(float);

    // copy input
    Copy(numSamples, out1, in1);

    unit->m_pos += numSamples;

    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        float fbufnum = ZIN0(1);
        uint32 bufnumL = (int)fbufnum;
        fbufnum = ZIN0(2);
        uint32 bufnumR = (int)fbufnum;
        unit->m_cflength = (int)ZIN0(5);

        SndBuf* bufL = ConvGetBuffer(unit, bufnumL, "StereoConvolution2L", numSamples);
        SndBuf* bufR = ConvGetBuffer(unit, bufnumR, "StereoConvolution2L", numSamples);

        if (!bufL || !bufL)
            return;

        unit->m_cfpos = 0;
        if (unit->m_curbuf == 1) {
            LOCK_SNDBUF2_SHARED(bufL, bufR);
            memcpy(unit->m_fftbuf2[0], bufL->data, framesize_f);
            memset(unit->m_fftbuf2[0] + unit->m_framesize, 0, framesize_f);
            scfft_dofft(unit->m_scfft2[0]);
            memcpy(unit->m_fftbuf2[1], bufR->data, framesize_f);
            memset(unit->m_fftbuf2[1] + unit->m_framesize, 0, framesize_f);
            scfft_dofft(unit->m_scfft2[1]);
        } else if (unit->m_curbuf == 0) {
            LOCK_SNDBUF2_SHARED(bufL, bufR);
            memcpy(unit->m_fftbuf3[0], bufL->data, framesize_f);
            memset(unit->m_fftbuf3[0] + unit->m_framesize, 0, framesize_f);
            scfft_dofft(unit->m_scfft3[0]);
            memcpy(unit->m_fftbuf3[1], bufR->data, framesize_f);
            memset(unit->m_fftbuf3[1] + unit->m_framesize, 0, framesize_f);
            scfft_dofft(unit->m_scfft3[1]);
        }
    }

    if (unit->m_pos & unit->m_framesize) {
        // have collected enough samples to transform next frame
        unit->m_pos = 0; // reset collection counter
        // copy to fftbuf
        memcpy(unit->m_fftbuf1, unit->m_inbuf1, framesize_f);

        // zero pad second part of buffer to allow for convolution
        memset(unit->m_fftbuf1 + unit->m_framesize, 0, framesize_f);
        // in place transform for now
        scfft_dofft(unit->m_scfft1);

        // complex multiply time
        int numbins = unit->m_fftsize >> 1; // unit->m_fftsize - 2 >> 1;

        float* p1 = unit->m_fftbuf1;
        float *p2L, *p2R;
        if (unit->m_curbuf == 0) {
            p2L = unit->m_fftbuf2[0];
            p2R = unit->m_fftbuf2[1];
        } else {
            p2L = unit->m_fftbuf3[0];
            p2R = unit->m_fftbuf3[1];
        }
        float* p3L = unit->m_tempbuf[0];
        float* p3R = unit->m_tempbuf[1];

        // complex multiply
        for (int i = 1; i < numbins; ++i) {
            float realL, imagL;
            float realR, imagR;
            int realind, imagind;
            realind = 2 * i;
            imagind = realind + 1;
            realL = p1[realind] * p2L[realind] - p1[imagind] * p2L[imagind];
            imagL = p1[realind] * p2L[imagind] + p1[imagind] * p2L[realind];
            realR = p1[realind] * p2R[realind] - p1[imagind] * p2R[imagind];
            imagR = p1[realind] * p2R[imagind] + p1[imagind] * p2R[realind];
            p3L[realind] = realL; // p2->bin[i];
            p3L[imagind] = imagL;
            p3R[realind] = realR; // p2->bin[i];
            p3R[imagind] = imagR;
        }

        for (int i = 0; i < 2; i++) {
            // copy second part from before to overlap
            memcpy(unit->m_overlapbuf[i], unit->m_outbuf[i] + unit->m_framesize, unit->m_framesize * sizeof(float));
            // inverse fft into outbuf
            memcpy(unit->m_outbuf[i], unit->m_tempbuf[i], unit->m_fftsize * sizeof(float));
            // in place
            scfft_doifft(unit->m_scfftR[i]);
        }


        if (unit->m_cfpos < unit->m_cflength) // do crossfade
        {
            if (unit->m_curbuf == 0) {
                p2L = unit->m_fftbuf3[0];
                p2R = unit->m_fftbuf3[1];
            } else {
                p2L = unit->m_fftbuf2[0];
                p2R = unit->m_fftbuf2[1];
            }

            // complex multiply
            for (int i = 1; i < numbins; ++i) {
                float realL, imagL;
                float realR, imagR;
                int realind, imagind;
                realind = 2 * i;
                imagind = realind + 1;
                realL = p1[realind] * p2L[realind] - p1[imagind] * p2L[imagind];
                imagL = p1[realind] * p2L[imagind] + p1[imagind] * p2L[realind];
                realR = p1[realind] * p2R[realind] - p1[imagind] * p2R[imagind];
                imagR = p1[realind] * p2R[imagind] + p1[imagind] * p2R[realind];
                p3L[realind] = realL; // p2->bin[i];
                p3L[imagind] = imagL;
                p3R[realind] = realR; // p2->bin[i];
                p3R[imagind] = imagR;
            }

            scfft_doifft(unit->m_scfftR2[0]);
            scfft_doifft(unit->m_scfftR2[1]);

            // now crossfade between outbuf and tempbuf
            float fact1 = (float)unit->m_cfpos / unit->m_cflength; // crossfade amount startpoint
            float rc = 1.f / (unit->m_cflength * unit->m_framesize); // crossfade amount increase per sample
            float* p4L = unit->m_outbuf[0];
            float* p5L = unit->m_tempbuf[0];
            float* p4R = unit->m_outbuf[1];
            float* p5R = unit->m_tempbuf[1];
            for (int i = 0; i < unit->m_framesize; i++) {
                float res;
                res = (1 - fact1) * p4L[i] + fact1 * p5L[i];
                p4L[i] = res;
                res = (1 - fact1) * p4R[i] + fact1 * p5R[i];
                p4R[i] = res;
                fact1 += rc;
            }
            if (unit->m_cflength == 1) // in the overlap the convolution is already at the new buffer
            {
                memcpy(unit->m_outbuf[0] + unit->m_framesize, unit->m_tempbuf[0] + unit->m_framesize,
                       unit->m_framesize * sizeof(float));
                memcpy(unit->m_outbuf[1] + unit->m_framesize, unit->m_tempbuf[1] + unit->m_framesize,
                       unit->m_framesize * sizeof(float));
            } else {
                for (int i = unit->m_framesize + 1; i < unit->m_fftsize; i++) {
                    float res;
                    res = (1 - fact1) * p4L[i] + fact1 * p5L[i];
                    p4L[i] = res;
                    res = (1 - fact1) * p4R[i] + fact1 * p5R[i];
                    p4R[i] = res;
                    fact1 += rc;
                }
            }
            unit->m_cfpos++;
            if (unit->m_cfpos == unit->m_cflength) // at end of crossfade, update the current buffer index
            {
                if (unit->m_curbuf == 0)
                    unit->m_curbuf = 1;
                else
                    unit->m_curbuf = 0;
            }
        }
    }

    // write out samples copied from outbuf, with overlap added in
    float* outputL = ZOUT(0);
    float* outputR = ZOUT(1);
    float* outL = unit->m_outbuf[0] + unit->m_pos;
    float* overlapL = unit->m_overlapbuf[0] + unit->m_pos;
    float* outR = unit->m_outbuf[1] + unit->m_pos;
    float* overlapR = unit->m_overlapbuf[1] + unit->m_pos;
    unit->m_prevtrig = curtrig;

    for (int i = 0; i < numSamples; ++i) {
        ZXP(outputL) = outL[i] + overlapL[i];
        ZXP(outputR) = outR[i] + overlapR[i];
    }
}

void Convolution3_Ctor(Convolution3* unit) {
    unit->m_framesize = (int)ZIN0(3);

    float fbufnum = ZIN0(1);
    uint32 bufnum = (int)fbufnum;

    SndBuf* buf = ConvGetBuffer(unit, bufnum, "Convolution3", 1);

    unit->m_inbuf1 = unit->m_inbuf2 = unit->m_outbuf = nullptr;

    if (buf) {
        if (unit->m_framesize <= 0) // if smaller than zero, equal to size of buffer
        {
            unit->m_framesize = buf->frames;
        }

        // just use memory for the input buffers and out buffer
        int framesize_f = unit->m_framesize * sizeof(float);

        unit->m_inbuf1 = (float*)RTAlloc(unit->mWorld, framesize_f);
        unit->m_inbuf2 = (float*)RTAlloc(unit->mWorld, framesize_f);
        unit->m_outbuf = (float*)RTAlloc(unit->mWorld, framesize_f);
        ClearUnitIfMemFailed(unit->m_inbuf1 && unit->m_inbuf2 && unit->m_outbuf);

        LOCK_SNDBUF_SHARED(buf);
        // calculate fft for kernel straight away
        memcpy(unit->m_inbuf2, buf->data, framesize_f);
        unit->m_pos = 0;

        memset(unit->m_outbuf, 0, framesize_f);
        unit->m_prevtrig = 0.f;
        if (INRATE(0) == calc_FullRate)
            SETCALC(Convolution3_next_a);
        else
            SETCALC(Convolution3_next_k);

        // initialize output
        OUT0(0) = IN0(0);
    }
}

void Convolution3_Dtor(Convolution3* unit) {
    RTFree(unit->mWorld, unit->m_inbuf1);
    RTFree(unit->mWorld, unit->m_inbuf2);
    RTFree(unit->mWorld, unit->m_outbuf);
}

void Convolution3_next_a(Convolution3* unit) {
    float* in = IN(0);
    float curtrig = ZIN0(2);

    float* pin1 = unit->m_inbuf1;

    int numSamples = unit->mWorld->mFullRate.mBufLength;

    // copy input
    Copy(numSamples, pin1, in);

    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        uint32 framesize_f = unit->m_framesize * sizeof(float);
        float fbufnum = ZIN0(1);
        uint32 bufnum = (int)fbufnum;
        SndBuf* buf = ConvGetBuffer(unit, bufnum, "Convolution3", numSamples);
        LOCK_SNDBUF_SHARED(buf);
        memcpy(unit->m_inbuf2, buf->data, framesize_f);
    }

    float* pin2 = unit->m_inbuf2;
    float* pout = unit->m_outbuf;
    int pos = unit->m_pos;
    int size = unit->m_framesize;

    for (int j = 0; j < numSamples; ++j) {
        float input = pin1[j];
        for (int i = 0; i < size; ++i) {
            int ind = (pos + i + j) % (size);
            pout[ind] = pout[ind] + pin2[i] * input;
        }
    }

    float* output = ZOUT(0);

    for (int i = 0; i < numSamples; ++i) {
        int ind = (pos + i) % (size);
        *++output = pout[ind];
    }

    pos = pos + numSamples;
    if (pos > size) {
        unit->m_pos = pos - size; // reset collection counter
    } else {
        unit->m_pos += numSamples;
    }
    unit->m_prevtrig = curtrig;
}


void Convolution3_next_k(Convolution3* unit) {
    float input = ZIN0(0);
    float curtrig = ZIN0(2);

    uint32 framesize_f = unit->m_framesize * sizeof(float);


    if (unit->m_prevtrig <= 0.f && curtrig > 0.f) {
        float fbufnum = ZIN0(1);
        uint32 bufnum = (int)fbufnum;
        SndBuf* buf = ConvGetBuffer(unit, bufnum, "Convolution3", 1);
        if (!buf)
            return;
        LOCK_SNDBUF_SHARED(buf);

        memcpy(unit->m_inbuf2, buf->data, framesize_f);
    }

    float* pin = unit->m_inbuf2;
    float* pout = unit->m_outbuf;
    int pos = unit->m_pos;
    int size = unit->m_framesize;

    for (int i = 0; i < size; ++i) {
        int ind = (pos + i) % (size);
        pout[ind] = pout[ind] + pin[i] * input;
    }

    float* output = OUT(0);
    *output = pout[pos];

    if (++pos > size)
        unit->m_pos = 0; // reset collection counter
    else
        unit->m_pos++;
    unit->m_prevtrig = curtrig;
}

void initConvolution(InterfaceTable* it) {
    DefineDtorUnit(Convolution);
    DefineDtorUnit(Convolution2);
    DefineDtorUnit(Convolution2L);
    DefineDtorUnit(StereoConvolution2L);
    DefineDtorUnit(Convolution3);
}
