~hecanjog/pippi

4cff9c3d79967bc89b6ecb8d54dc454f4bb70b06 — Erik Schoster 4 months ago dbc71a0
libpippi & pippi microsound & fx updates:

libpippi:
- remove some old example programs
- refactor LPFormation to use tapeosc-driven grains
- use input buffer length to set initial range on LPTapeOsc init
- tapeosc phasor is now a position (0-1) rather than a buf index
- add WIN_NONE built-in window (just fills the buffer with 1s)
- port the supercollider crossover distortion over from cython
- handle float promotion issues in the butterworth filters & fx_fold
- expose some more params on the tapeosc ugen wrapper

pippi:
- refactor grainclouds to use new tapeosc formations from libpippi
M libpippi/Makefile => libpippi/Makefile +0 -12
@@ 58,9 58,6 @@ osc-examples:
	echo "Building pulsarosc.c example...";
	gcc $(LPFLAGS) examples/pulsarosc.c $(LPSOURCES) $(LPLIBS) -o build/pulsarosc

	#echo "Building pulsarosc2.c example...";
	#gcc $(LPFLAGS) examples/pulsarosc2.c $(LPSOURCES) $(LPLIBS) -o build/pulsarosc2

	echo "Building phasorosc.c example...";
	gcc $(LPFLAGS) examples/phasorosc.c $(LPSOURCES) $(LPLIBS) -o build/phasorosc



@@ 85,9 82,6 @@ osc-examples:
	echo "Building tapeosc.c example...";
	gcc $(LPFLAGS) examples/tapeosc.c $(LPSOURCES) $(LPLIBS) -o build/tapeosc

	echo "Building tapeosc2.c example...";
	gcc $(LPFLAGS) examples/tapeosc2.c $(LPSOURCES) $(LPLIBS) -o build/tapeosc2

	echo "Building additive_synthesis.c example...";
	gcc $(LPFLAGS) examples/additive_synthesis.c $(LPSOURCES) $(LPLIBS) -o build/additive_synthesis



@@ 152,12 146,6 @@ microsound-examples:
	echo "Building grainformation.c example...";
	gcc $(LPFLAGS) examples/grainformation.c $(LPSOURCES) $(LPLIBS) -o build/grainformation

	echo "Building grainformation2.c example...";
	gcc $(LPFLAGS) examples/grainformation2.c $(LPSOURCES) $(LPLIBS) -o build/grainformation2

	echo "Building grainformation3.c example...";
	gcc $(LPFLAGS) examples/grainformation3.c $(LPSOURCES) $(LPLIBS) -o build/grainformation3

convolution-examples:
	mkdir -p build renders


M libpippi/examples/grainformation.c => libpippi/examples/grainformation.c +19 -33
@@ 4,52 4,38 @@
#define CHANNELS 2

int main() {
    size_t i, c, length, numlayers, grainlength;
    lpbuffer_t * fake_input;
    lpbuffer_t * out;
    lpbuffer_t * freq;
    lpbuffer_t * amp;
    size_t i, length;
    lpbuffer_t * out, * src, * win;
    lpformation_t * formation;
    lpsineosc_t * osc;
    int window_type;
    int c, numgrains=6;

    length = 10 * SR;
    numlayers = 1;
    grainlength = SR/10.;
    window_type = WIN_HANN;
    src = LPSoundFile.read("../tests/sounds/living.wav");
    win = LPWindow.create(WIN_HANN, 4096);

    out = LPBuffer.create(length, CHANNELS, SR);
    formation = LPFormation.create(window_type, numlayers, grainlength, length, CHANNELS, SR, NULL);

    /* Render a sine tone and fill the ringbuffer with it, 
     * to simulate a live input. */
    osc = LPSineOsc.create();
    osc->samplerate = SR;

    freq = LPParam.from_float(440.0f);
    amp = LPParam.from_float(0.8f);

    fake_input = LPSineOsc.render(osc, length, freq, amp, CHANNELS);
    LPRingBuffer.write(formation->rb, fake_input);

    LPSoundFile.write("renders/grainformation-rb-out.wav", formation->rb);
    length = 600 * src->samplerate;
    out = LPBuffer.create(length, src->channels, src->samplerate);
    formation = LPFormation.create(numgrains, src, win);

    /* Render each frame of the grainformation */
    for(i=0; i < length; i++) {
        if(LPRand.rand(0,1) > 0.99f) {
            formation->offset += LPRand.rand(0, LPRand.rand(.000005f, .0001f));
            formation->speed = LPRand.randint(0,6)*0.5f+0.125f;
            formation->grainlength = LPRand.rand(0.01f, 0.5f);
        }
        LPFormation.process(formation);
        for(c=0; c < CHANNELS; c++) {
            out->data[i * CHANNELS + c] = formation->current_frame->data[c];

        for(c=0; c < out->channels; c++) {
            out->data[i * out->channels + c] = formation->current_frame->data[c];
        }
    }

    printf("wat %f\n", amp->data[0]);
    LPFX.norm(out, 0.8f);
    LPSoundFile.write("renders/grainformation-out.wav", out);

    LPSineOsc.destroy(osc);
    LPBuffer.destroy(freq);
    LPBuffer.destroy(amp);
    LPBuffer.destroy(out);
    LPBuffer.destroy(fake_input);
    LPBuffer.destroy(src);
    LPBuffer.destroy(win);
    LPFormation.destroy(formation);

    return 0;

D libpippi/examples/grainformation2.c => libpippi/examples/grainformation2.c +0 -75
@@ 1,75 0,0 @@
#include "pippi.h"

#define SR 48000
#define CHANNELS 2

int main() {
    size_t i, c, length;
    lpbuffer_t * snd;
    lpbuffer_t * out;
    lpbuffer_t * pw;
    int window_type;

    /*
    lpmultishapeosc_t * cutoffs;
    */
    lpformation_t * formation;

    /*
    lpfloat_t cutoff;
    lpfloat_t ys[CHANNELS];
    */

    window_type = WIN_HANN;
    length = 10 * SR;

    /*
    ys[0] = 0.f;
    ys[1] = 0.f;
    */

    LPRand.seed(112244);

    out = LPBuffer.create(length, CHANNELS, SR);
    snd = LPSoundFile.read("../tests/sounds/living.wav");

    formation = LPFormation.create(window_type, 1, SR/20.f, length, CHANNELS, SR, NULL);
    formation->speed = 1.f;

    /*
    cutoffs = LPShapeOsc.multi(4, WT_COS, WT_TRI, WT_SINE, WT_SINE);
    cutoffs->min = 20.0f;
    cutoffs->max = 20000.0f;
    cutoffs->maxfreq = 3.f;
    */

    pw = LPWindow.create(WIN_SINE, 4096);
    LPBuffer.scale(pw, 0, 1, 0.f, 0.5f);

    LPRingBuffer.write(formation->rb, snd);

    /* Render each frame of the grainformation */
    for(i=0; i < length; i++) {
        /*formation->pulsewidth = LPInterpolation.linear_pos(pw, (float)i / length);*/
        LPFormation.process(formation);
        /*
        cutoff = LPShapeOsc.multiprocess(cutoffs);
        */

        for(c=0; c < CHANNELS; c++) {
            /*out->data[i * CHANNELS + c] = LPFX.lpf1(formation->current_frame->data[c] * 0.5f, &ys[c], cutoff, SR);*/
            out->data[i * CHANNELS + c] = formation->current_frame->data[c] * 0.5f;
        }
    }

    LPSoundFile.write("renders/grainformation2-out.wav", out);

    LPBuffer.destroy(out);
    LPBuffer.destroy(snd);
    LPFormation.destroy(formation);
    /*
    LPShapeOsc.multidestroy(cutoffs);
    */

    return 0;
}

D libpippi/examples/grainformation3.c => libpippi/examples/grainformation3.c +0 -64
@@ 1,64 0,0 @@
#include "pippi.h"

#define SR 48000
#define CHANNELS 2

int main() {
    size_t i, c, length;
    lpbuffer_t * snd;
    lpbuffer_t * out;
    lpbuffer_t * win;
    lpbuffer_t * pw;
    int window_type;

    lpmultishapeosc_t * cutoffs;
    lpformation_t * formation;

    lpfloat_t cutoff;
    lpfloat_t ys[CHANNELS];

    window_type = WIN_USER;
    length = 10 * SR;

    ys[0] = 0.f;
    ys[1] = 0.f;

    LPRand.seed(112244);

    win = LPWindow.create(WIN_HANN, 4096);
    out = LPBuffer.create(length, CHANNELS, SR);
    snd = LPSoundFile.read("../tests/sounds/living.wav");

    formation = LPFormation.create(window_type, 1, SR/4.f, length, CHANNELS, SR, win);
    formation->speed = 1.f;

    cutoffs = LPShapeOsc.multi(4, WT_COS, WT_TRI, WT_SINE, WT_SINE);
    cutoffs->min = 20.0f;
    cutoffs->max = 20000.0f;
    cutoffs->maxfreq = 3.f;

    pw = LPWindow.create(WIN_SINE, 4096);
    LPBuffer.scale(pw, 0, 1, 0.f, 0.5f);

    LPRingBuffer.write(formation->rb, snd);

    /* Render each frame of the grainformation */
    for(i=0; i < length; i++) {
        formation->pulsewidth = LPInterpolation.linear_pos(pw, (float)i / length);
        LPFormation.process(formation);
        cutoff = LPShapeOsc.multiprocess(cutoffs);

        for(c=0; c < CHANNELS; c++) {
            out->data[i * CHANNELS + c] = LPFX.lpf1(formation->current_frame->data[c] * 0.5f, &ys[c], cutoff, SR);
        }
    }

    LPSoundFile.write("renders/grainformation3-out.wav", out);

    LPBuffer.destroy(out);
    LPBuffer.destroy(snd);
    LPFormation.destroy(formation);
    LPShapeOsc.multidestroy(cutoffs);

    return 0;
}

D libpippi/examples/pulsarosc2.c => libpippi/examples/pulsarosc2.c +0 -66
@@ 1,66 0,0 @@
#include "pippi.h"

#define CHANNELS 2
#define SR 48000
#define TABLESIZE 128

#define NUMWT 3
#define NUMWIN 2
#define WTSIZE 4096

int main() {
    size_t length = SR * 10;
    size_t i, c;
    lpbuffer_t * wt;
    lpbuffer_t * win;

    lppulsarosc_t * osc;
    lpfloat_t freq = 220.f;
    lpfloat_t sample = 0;

    size_t wt_onsets[NUMWT] = {};
    size_t wt_lengths[NUMWT] = {};
    size_t win_onsets[NUMWIN] = {};
    size_t win_lengths[NUMWIN] = {};

    wt = LPWavetable.create_stack(NUMWT, 
            wt_onsets, wt_lengths,
            WT_SINE, WTSIZE,
            WT_RND, WTSIZE,
            WT_TRI, WTSIZE
    );

    win = LPWindow.create_stack(NUMWIN, 
            win_onsets, win_lengths,
            WIN_HANN, WTSIZE,
            WIN_SINE, WTSIZE
    );

    lpbuffer_t * buf = LPBuffer.create(length, CHANNELS, SR);    

    osc = LPPulsarOsc.create(
        NUMWT, wt->data, wt->length, wt_onsets, wt_lengths, 
        NUMWIN, win->data, win->length, win_onsets, win_lengths
    );

    osc->samplerate = SR;
    osc->freq = freq;

    for(i=0; i < length; i++) {
        sample = LPPulsarOsc.process(osc);
        for(c=0; c < CHANNELS; c++) {
            buf->data[i * CHANNELS + c] = sample * 0.1f;
        }
    }

    LPSoundFile.write("renders/pulsarosc2-out.wav", buf);

    LPPulsarOsc.destroy(osc);
    LPBuffer.destroy(buf);
    LPBuffer.destroy(wt);
    LPBuffer.destroy(win);

    return 0;
}



M libpippi/examples/tapeosc.c => libpippi/examples/tapeosc.c +1 -1
@@ 26,7 26,7 @@ int main() {
    LPBuffer.scale(speeds, 0, 1, 0.5f, 2.f);

    out = LPBuffer.create(length, CHANNELS, SR);
    osc = LPTapeOsc.create(sine, SR);
    osc = LPTapeOsc.create(sine);
    osc->samplerate = SR;

    speedphaseinc = 1.f/speeds->length;

D libpippi/examples/tapeosc2.c => libpippi/examples/tapeosc2.c +0 -50
@@ 1,50 0,0 @@
#include "pippi.h"

#define BS 4096
#define SR 48000
#define CHANNELS 2

int main() {
    size_t i, c, length;
    lpbuffer_t * snd;
    lpbuffer_t * out;
    lpbuffer_t * speeds;
    lptapeosc_t * osc;
    lpfloat_t speedphase, speedphaseinc;

    length = 60 * SR;

    snd = LPSoundFile.read("../tests/sounds/living.wav");

    speeds = LPWindow.create(WIN_HANN, BS);
    LPBuffer.scale(speeds, 0, 1, SR/10.f, (float)SR);

    out = LPBuffer.create(length, CHANNELS, SR);
    osc = LPTapeOsc.create(snd, snd->length);
    osc->samplerate = SR;

    speedphaseinc = 1.f/speeds->length;
    speedphase = 0.f;

    for(i=0; i < length; i++) {
        /*osc->range = LPInterpolation.linear(speeds, ((float)i/length) * speeds->length);*/

        LPTapeOsc.process(osc);
        for(c=0; c < CHANNELS; c++) {
            out->data[i * CHANNELS + c] = osc->current_frame->data[c];
        }
        speedphase += speedphaseinc;
        if(speedphase >= speeds->length) {
            speedphase -= speeds->length;
        }
    }

    LPSoundFile.write("renders/tapeosc2-out.wav", out);

    LPTapeOsc.destroy(osc);
    LPBuffer.destroy(out);
    LPBuffer.destroy(snd);
    LPBuffer.destroy(speeds);

    return 0;
}

M libpippi/src/microsound.c => libpippi/src/microsound.c +85 -154
@@ 1,189 1,123 @@
#include "microsound.h"

void grain_process(lpgrain_t * g, lpbuffer_t * out) {
    lpfloat_t sample, ipw, phase, window_phase;
    int c;
    int c, oc=0;
    lpfloat_t win=0.f;

    ipw = 1.f;
    if(g->pulsewidth > 0) ipw = 1.0f/g->pulsewidth;
    assert(!isnan(g->src->phase));
    assert(g->samplerate > 0);
    assert(g->grainlength > 0);
    assert(out->length == 1); // outbuf is just a single frame vector

    phase = g->phase * ipw + g->start;
    if(g->pulsewidth <= 0) return;

    window_phase = (g->phase / g->length)  * ipw;
    while(window_phase >= 1.f) window_phase -= 1.f;
    window_phase *= g->window->length;
    g->src->range = g->grainlength * g->samplerate;
    g->src->start = g->offset * g->samplerate;
    g->win->phase = g->src->phase; 
    g->src->pulsewidth = g->pulsewidth; 
    g->win->pulsewidth = g->pulsewidth; 

    if(ipw > 0.f && phase < g->buf->length - 1) {
        for(c=0; c < g->buf->channels; c++) {
            sample = LPInterpolation.linear_channel(g->buf, phase, c);
            sample *= LPInterpolation.linear(g->window, window_phase);
    LPTapeOsc.process(g->src); 
    LPTapeOsc.process(g->win); 

            if(g->pan != 0.5f) {
                if((2-(c & 1)) == 1) { /* checks if c is odd */
                    sample *= (1.f - g->pan);
                } else {
                    sample *= g->pan;
                }
            }
    g->gate = g->src->gate;

            out->data[c] += sample * g->amp;
        }
    /* window sources always get mixed to mono */
    for(c=0; c < g->win->buf->channels; c++) {
        win += g->win->current_frame->data[c];
    }

    g->phase += g->speed;

    if(g->phase >= g->length) {
        /* recycle this grain, we're done with it now */
        g->phase = 0;
        g->in_use = 0; 
    /* grain sources get mapped to the grain output channels */
    for(c=0; c < out->channels; c++) {
        oc = c; 
        while(oc >= g->src->current_frame->channels) oc -= g->src->current_frame->channels;
        out->data[c] += g->src->current_frame->data[oc] * win;
    }
}

lpformation_t * formation_create(int window_type, int numlayers, size_t grainlength, size_t rblength, int channels, int samplerate, lpbuffer_t * user_window) {
    lpformation_t * formation;

    formation = (lpformation_t *)LPMemoryPool.alloc(1, sizeof(lpformation_t));
    if(window_type == WIN_USER) {
        formation->window = user_window;
    } else {
        formation->window = LPWindow.create(window_type, 4096);
    }

    formation->rb = LPRingBuffer.create(rblength, channels, samplerate);
    formation->grainlength = grainlength;
    formation->numlayers = numlayers;
    formation->amp = 1.f;
    formation->current_frame = LPBuffer.create(1, channels, samplerate);
    formation->speed = 1.f;
    formation->scrub = 1.f;
    formation->spread = 0.f;
    formation->skew = 0.f;
    formation->pulsewidth = 1.f;
    formation->pos = 0.f;
    formation->pan = 0.5f;

    formation->num_active_grains = 0;

    formation->phase = 0.f;
    formation->phase_inc = 1.f / samplerate;

    formation->onset_phase = 0.f;
    formation->onset_phase_inc = 1.f / rblength;

    formation->graininterval = grainlength / 2;

    return formation;
void grain_init(lpgrain_t * grain, lpbuffer_t * src, lpbuffer_t * win) {
    win->samplerate = src->samplerate;
    grain->src = LPTapeOsc.create(src);
    grain->win = LPTapeOsc.create(win);
    grain->offset = 0.f;
    grain->channels = src->channels;
    grain->samplerate = src->samplerate;
    grain->pulsewidth = 1.f;
}

void formation_process(lpformation_t * c) {
    int i, g, gi, active_grains, activated_grains, requested_grains, new_grain;
    size_t grainlength;
    lpfloat_t pan;
    int grain_indexes[LPFORMATION_MAXGRAINS];

    for(i=0; i < c->current_frame->channels; i++) {
        c->current_frame->data[i] = 0.f;
lpformation_t * formation_create(int numgrains, lpbuffer_t * src, lpbuffer_t * win) {
    lpformation_t * f;
    lpfloat_t phaseinc, grainlength=.3f, pulsewidth=1.f,
              offset=0.f, speed=1.f, pan=0.5f;
    int g;

    assert(src->samplerate > 0);
    assert(src->length > 0);
    assert(src->channels > 0);
    assert(numgrains < LPFORMATION_MAXGRAINS);

    f = (lpformation_t *)LPMemoryPool.alloc(1, sizeof(lpformation_t));
    f->current_frame = LPBuffer.create(1, src->channels, src->samplerate);
    f->numgrains = numgrains;
    f->grainlength = grainlength;
    f->pulsewidth = pulsewidth;
    f->offset = offset;
    f->speed = speed;
    f->pan = pan;
    
    phaseinc = 1.f/numgrains;
    for(g=0; g < numgrains; g++) {
        grain_init(&f->grains[g], src, win);
        f->grains[g].grainlength = grainlength;
        f->grains[g].offset = offset;
        f->grains[g].pulsewidth = pulsewidth;
        f->grains[g].pan = pan;
        f->grains[g].src->speed = speed;
        f->grains[g].src->phase = g*phaseinc;
        f->grains[g].win->phase = f->grains[g].src->phase;
    }

    /* calculate the grid phase */

    /* if the grid phase has reset, add some grains to the stack 
     * at this point, formation params are taken as a snapshot and 
     * copied into the new grains.
     * */
    requested_grains = 0;
    if(c->onset_phase >= 1.f) {
        requested_grains = c->numlayers;
    }
    return f;
}

    /* adding a grain to the stack: */
    /* reset grain indexes, loop over to find active indexes, 
     * use first open indexes for new grains */
    active_grains = 0;
    activated_grains = 0;
    g = 0;
    gi = 0;
void formation_process(lpformation_t * f) {
    int g;

    if(requested_grains == 0 && c->num_active_grains == 0) {
        requested_grains += 1;
    }
    memset(f->current_frame->data, 0, sizeof(lpfloat_t) * f->current_frame->channels);

    while(1) {
        if(active_grains == 0 && requested_grains == 0) break;
    for(g=0; g < f->numgrains; g++) {
        grain_process(&f->grains[g], f->current_frame);
        if(f->grains[g].gate) {
            f->grains[g].pulsewidth = f->pulsewidth;
            f->grains[g].src->speed = f->speed;

        new_grain = 0;
        if(c->grains[g].in_use == 0 && activated_grains < requested_grains) {
            if(c->grainlength_jitter > 0) {
                grainlength = c->grainlength + (size_t)LPRand.rand(0, c->grainlength_jitter * c->grainlength_maxjitter);
            if(f->spread > 0) {
                f->grains[g].pan = .5f + LPRand.rand(-.5f, .5f) * f->spread;
            } else {
                grainlength = c->grainlength;
                f->grains[g].pan = f->pan;
            }

            /* add a new grain to the stack by reusing this one */
            c->grains[g].in_use = 1;
            c->grains[g].phase = 0;

            c->grains[g].buf = c->rb;
            c->grains[g].offset = c->offset / (lpfloat_t)c->rb->samplerate;
            c->grains[g].pulsewidth = c->pulsewidth;

            c->grains[g].phase = 0.f;
            c->grains[g].phase_offset = LPRand.rand(0.f, 1.f);

            c->grains[g].amp = c->amp;

            c->grains[g].window = c->window;

            c->grains[g].skew = c->skew;
            c->grains[g].speed = c->speed;

            c->grains[g].length = grainlength;
            c->grains[g].range = grainlength;
            c->grains[g].start = (size_t)(c->rb->length * c->pos);

            c->grains[g].pan = c->pan;
            if(c->spread > 0) {
                pan = 0.5f + LPRand.rand(-.5f, 0.5f) * c->spread;
                c->grains[g].pan = pan;
            if(f->grainlength_jitter > 0) {
                f->grains[g].grainlength = f->grainlength + (size_t)LPRand.rand(0, f->grainlength_jitter * f->grainlength_maxjitter);
            } else {
                f->grains[g].grainlength = f->grainlength;
            }

            activated_grains += 1;
            new_grain = 1;
        }
            if(f->grid_jitter > 0) {
                f->grains[g].offset = f->offset + (size_t)LPRand.rand(0, f->grid_jitter * f->grid_maxjitter);
            } else {
                f->grains[g].offset = f->offset;
            }

        if(c->grains[g].in_use == 1) {
            grain_indexes[gi] = g;
            gi += 1;
            if(new_grain == 0) active_grains += 1;
        }

        g += 1;
        if(g >= LPFORMATION_MAXGRAINS) g -= LPFORMATION_MAXGRAINS;
        if(gi >= LPFORMATION_MAXGRAINS) break;
        if(active_grains + activated_grains >= c->num_active_grains + requested_grains) break;
    }

    /* process all the active grains into the output buffer */
    active_grains += activated_grains;
    c->num_active_grains = active_grains;
    for(gi=0; gi < active_grains; gi++) {
        grain_process(&c->grains[grain_indexes[gi]], c->current_frame);
    }

    /* increment the internal phases */
    c->phase += c->phase_inc;
    while(c->phase >= 1.f) c->phase -= 1.f;

    c->onset_phase += c->onset_phase_inc;
    while(c->onset_phase >= 1.f) c->onset_phase -= 1.f;

    c->pos += c->phase_inc;
    while(c->pos >= 1.f) c->pos -= 1.f;
    while(f->offset >= f->grains[0].src->buf->length) f->offset -= f->grains[0].src->buf->length;
}

void formation_destroy(lpformation_t * c) {
    LPBuffer.destroy(c->window);
    LPBuffer.destroy(c->rb);
    LPBuffer.destroy(c->source);
    LPBuffer.destroy(c->current_frame);
    LPMemoryPool.free(c);
}


@@ 241,7 175,4 @@ int extract_wavesets(
    return 0;
}


const lpformation_factory_t LPFormation = { formation_create, formation_process, formation_destroy };



M libpippi/src/microsound.h => libpippi/src/microsound.h +15 -24
@@ 9,62 9,53 @@
typedef struct lpgrain_t {
    size_t length;
    int channels;
    lpfloat_t samplerate; 
    lpfloat_t pulsewidth; 
    lpfloat_t grainlength;
    lpfloat_t offset; /* in seconds */

    size_t range;
    size_t start;
    size_t offset;

    lpfloat_t phase_offset;
    lpfloat_t phase;
    lpfloat_t pan;
    lpfloat_t amp;
    lpfloat_t speed;
    lpfloat_t skew; /* phase distortion on the grain window */

    int in_use;
    int gate;

    lpbuffer_t * buf;
    lpbuffer_t * window;
    lptapeosc_t * src;
    lptapeosc_t * win;
} lpgrain_t;

typedef struct lpformation_t {
    lpgrain_t grains[LPFORMATION_MAXGRAINS];
    int num_active_grains;
    size_t numlayers;
    size_t grainlength;
    int numgrains;
    lpfloat_t grainlength;
    lpfloat_t grainlength_maxjitter;
    lpfloat_t grainlength_jitter; /* 0-1 proportional to grainlength_maxjitter */
    size_t graininterval;
    lpfloat_t grid_maxjitter;
    lpfloat_t grid_jitter;

    lpfloat_t spread; /* pan spread */
    lpfloat_t speed;
    lpfloat_t scrub;
    lpfloat_t offset;
    lpfloat_t offset; /* in seconds */
    lpfloat_t skew;
    lpfloat_t amp;
    lpfloat_t pan;
    lpfloat_t pulsewidth; 

    lpfloat_t phase; /* internal phase */
    lpfloat_t phase_inc;
    lpfloat_t pos; /* sample start position in source buffer: 0-1 */

    lpfloat_t onset_phase;
    lpfloat_t onset_phase_inc;

    lpbuffer_t * source;
    lpbuffer_t * window;
    lpbuffer_t * current_frame;
    lpbuffer_t * rb;
} lpformation_t;

typedef struct lpformation_factory_t {
    lpformation_t * (*create)(int window_type, int numlayers, size_t grainlength, size_t rblength, int channels, int samplerate, lpbuffer_t * user_window);
    lpformation_t * (*create)(int numgrains, lpbuffer_t * src, lpbuffer_t * win);
    void (*process)(lpformation_t *);
    void (*destroy)(lpformation_t *);
} lpformation_factory_t;

void grain_init(lpgrain_t * grain, lpbuffer_t * src, lpbuffer_t * win);
void grain_process(lpgrain_t * g, lpbuffer_t * out);

extern const lpformation_factory_t LPFormation;

#endif

M libpippi/src/oscs.tape.c => libpippi/src/oscs.tape.c +26 -68
@@ 1,6 1,6 @@
#include "oscs.tape.h"

lptapeosc_t * create_tapeosc(lpbuffer_t * buf, lpfloat_t range);
lptapeosc_t * create_tapeosc(lpbuffer_t * buf);
void process_tapeosc(lptapeosc_t * osc);
void rewind_tapeosc(lptapeosc_t * osc);
lpbuffer_t * render_tapeosc(lptapeosc_t * osc, size_t length, lpbuffer_t * amp, int channels);


@@ 14,111 14,69 @@ const lptapeosc_factory_t LPTapeOsc = {
    destroy_tapeosc 
};

lptapeosc_t * create_tapeosc(lpbuffer_t * buf, lpfloat_t range) {
    lpfloat_t samplerate;
    int channels;
lptapeosc_t * create_tapeosc(lpbuffer_t * buf) {
    assert(buf != NULL);
    assert(buf->channels > 0);
    assert(buf->samplerate > 0);

    lptapeosc_t* osc = (lptapeosc_t*)LPMemoryPool.alloc(1, sizeof(lptapeosc_t));
    osc->buf = buf;

    if(buf == NULL) {
        samplerate = (lpfloat_t)DEFAULT_SAMPLERATE;
        channels = DEFAULT_CHANNELS;
    } else {
        samplerate = buf->samplerate;
        channels = buf->channels;
    }

    osc->samplerate = samplerate;
    osc->range = range;
    osc->samplerate = buf->samplerate;
    osc->range = buf->length;
    osc->gate = 0;
    osc->pulsewidth = 1.f;

    osc->phase = 0.f;
    osc->speed = 1.f;
    osc->start = 0.f;
    osc->start_increment = range;
    osc->current_frame = LPBuffer.create(1, channels, samplerate);
    osc->current_frame = LPBuffer.create(1, buf->channels, buf->samplerate);
    return osc;
}

void rewind_tapeosc(lptapeosc_t * osc) {
    osc->start = 0;
    osc->phase = osc->phase - (int)osc->phase;
    osc->phase = 0;
}

void process_tapeosc(lptapeosc_t * osc) {
    lpfloat_t sample, f, a, b;
    lpfloat_t sample, f, a, b, phase, ipw=0.f;
    int c, channels;
    size_t idxa, idxb, boundry;
    size_t idxa, idxb;

    channels = osc->buf->channels;
    boundry = osc->range + osc->start;
    assert(osc->range != 0);

    //printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);

    f = osc->phase - (int)osc->phase;
    idxa = (size_t)osc->phase;
    idxb = idxa + 1;

    for(c=0; c < channels; c++) {
        a = osc->buf->data[idxa * channels + c];
        b = osc->buf->data[idxb * channels + c];
        sample = (1.f - f) * a + (f * b);
        osc->current_frame->data[c] = sample;
    }

    osc->phase += osc->speed;
    //printf("phase %f channels %i boundry %ld speed %f\n", osc->phase, channels, boundry, osc->speed);

    if(osc->phase >= boundry) {
        osc->phase = osc->start;
        osc->gate = 1;
    } else {
        osc->gate = 0;
    }
}

/*
void process_tapeosc(lptapeosc_t * osc) {
    lpfloat_t sample, f, a, b, ipw, phase;
    int c, channels;
    size_t idxa, idxb, boundry;

    ipw = 1.f;
    if(osc->pulsewidth > 0) ipw = 1.0f/osc->pulsewidth;

    phase = osc->phase * ipw;
    channels = osc->buf->channels;
    boundry = osc->range + osc->start;
    if(osc->pulsewidth > 0 && osc->phase < osc->pulsewidth) {
        ipw = 1.f/osc->pulsewidth;

    f = phase - (int)phase;
    idxa = (size_t)phase;
    idxb = idxa + 1;
        phase = osc->phase * (osc->range * ipw) + osc->start;
        while(phase >= osc->buf->length-1) phase -= osc->buf->length-1;

        f = phase - (int)phase;
        idxa = (size_t)phase;
        idxb = idxa + 1;

    if(ipw == 0.f || phase >= osc->buf->length - 1) {
        for(c=0; c < channels; c++) {
            osc->current_frame->data[c] = 0.f;
        }
    } else {
        for(c=0; c < channels; c++) {
            a = osc->buf->data[idxa * channels + c];
            b = osc->buf->data[idxb * channels + c];
            sample = (1.f - f) * a + (f * b);
            osc->current_frame->data[c] = sample;
        }
    } else {
        memset(osc->current_frame->data, 0, sizeof(lpfloat_t) * channels);
    }

    osc->phase += osc->speed;
    osc->phase += osc->speed * (1.f/osc->range) * osc->pulsewidth;

    if(osc->phase >= boundry) {
        osc->phase = osc->start;
    if(osc->phase >= 1.f) {
        osc->gate = 1;
    } else {
        osc->gate = 0;
    }

    while(osc->phase >= 1.f) osc->phase -= 1.f;
}
*/

lpbuffer_t * render_tapeosc(lptapeosc_t * osc, size_t length, lpbuffer_t * amp, int channels) {
    lpbuffer_t * out;

M libpippi/src/oscs.tape.h => libpippi/src/oscs.tape.h +1 -2
@@ 9,7 9,6 @@ typedef struct lptapeosc_t {
    lpfloat_t pulsewidth;
    lpfloat_t samplerate;
    lpfloat_t start;
    lpfloat_t start_increment;
    lpfloat_t range;
    lpbuffer_t * buf;
    lpbuffer_t * current_frame;


@@ 17,7 16,7 @@ typedef struct lptapeosc_t {
} lptapeosc_t;

typedef struct lptapeosc_factory_t {
    lptapeosc_t * (*create)(lpbuffer_t *, lpfloat_t);
    lptapeosc_t * (*create)(lpbuffer_t *);
    void (*process)(lptapeosc_t *);
    void (*rewind)(lptapeosc_t *);
    lpbuffer_t * (*render)(lptapeosc_t *, size_t, lpbuffer_t *, int);

M libpippi/src/pippiconstants.h => libpippi/src/pippiconstants.h +1 -0
@@ 85,6 85,7 @@ enum Wavetables {
};

enum Windows {
    WIN_NONE,
    WIN_SINE,
    WIN_SINEIN,
    WIN_SINEOUT,

M libpippi/src/pippicore.c => libpippi/src/pippicore.c +29 -10
@@ 69,6 69,7 @@ lpfloat_t fx_hpf1(lpfloat_t x, lpfloat_t * y, lpfloat_t cutoff, lpfloat_t sample
void fx_convolve(lpbuffer_t * a, lpbuffer_t * b, lpbuffer_t * out);
void fx_norm(lpbuffer_t * buf, lpfloat_t ceiling);
lpfloat_t fx_fold(lpfloat_t val, lpfloat_t * prev, lpfloat_t samplerate);
lpfloat_t fx_crossover(lpfloat_t val, lpfloat_t amount, lpfloat_t smooth, lpfloat_t fade);
lpfloat_t fx_limit(lpfloat_t val, lpfloat_t * prev, lpfloat_t threshold, lpfloat_t release, lpbuffer_t * del);
lpfloat_t fx_crush(lpfloat_t val, int bits);
lpbfilter_t * fx_butthp_create(lpfloat_t cutoff, lpfloat_t samplerate);


@@ 133,7 134,7 @@ const lpparam_factory_t LPParam = { param_create_from_float, param_create_from_i
const lpwavetable_factory_t LPWavetable = { create_wavetable, create_wavetable_stack, destroy_wavetable };
const lpwindow_factory_t LPWindow = { create_window, create_window_stack, destroy_window };
const lpringbuffer_factory_t LPRingBuffer = { ringbuffer_create, ringbuffer_fill, ringbuffer_read, ringbuffer_readinto, ringbuffer_writefrom, ringbuffer_write, ringbuffer_readone, ringbuffer_writeone, ringbuffer_dub, ringbuffer_destroy };
const lpfx_factory_t LPFX = { read_skewed_buffer, fx_lpf1, fx_hpf1, fx_convolve, fx_norm, fx_fold, fx_limit, fx_crush };
const lpfx_factory_t LPFX = { read_skewed_buffer, fx_lpf1, fx_hpf1, fx_convolve, fx_norm, fx_crossover, fx_fold, fx_limit, fx_crush };
const lpfilter_factory_t LPFilter = { fx_butthp_create, fx_butthp, fx_buttlp_create, fx_buttlp };

/* Platform-specific random seed, called 


@@ 1289,7 1290,7 @@ lpbfilter_t * fx_butthp_create(lpfloat_t cutoff, lpfloat_t samplerate) {

    filter->sr = samplerate;
    filter->freq = cutoff;
    filter->pidsr = PI / samplerate;
    filter->pidsr = (lpfloat_t)PI / samplerate;

    return filter;
}


@@ 1307,11 1308,11 @@ lpfloat_t fx_butthp(lpbfilter_t * filter, lpfloat_t in) {
        c = tanf(filter->pidsr * filter->lkf);
#endif
        
      filter->a[1] = 1.f / (1.f + ROOT2 * c + c * c);
      filter->a[1] = 1.f / (1.f + (lpfloat_t)ROOT2 * c + c * c);
      filter->a[2] = -(filter->a[1] + filter->a[1]);
      filter->a[3] = filter->a[1];
      filter->a[4] = 2.f * (c*c - 1.f) * filter->a[1];
      filter->a[5] = (1.f - ROOT2 * c + c * c) * filter->a[1];
      filter->a[5] = (1.f - (lpfloat_t)ROOT2 * c + c * c) * filter->a[1];
    }

    t = in - filter->a[4] * filter->a[6] - filter->a[5] * filter->a[7];


@@ 1329,7 1330,7 @@ lpbfilter_t * fx_buttlp_create(lpfloat_t cutoff, lpfloat_t samplerate) {

    filter->sr = samplerate;
    filter->freq = cutoff;
    filter->pidsr = PI / samplerate;
    filter->pidsr = (lpfloat_t)PI / samplerate;

    return filter;
}


@@ 1347,11 1348,11 @@ lpfloat_t fx_buttlp(lpbfilter_t * filter, lpfloat_t in) {
        c = 1.f / tanf(filter->pidsr * filter->lkf);
#endif
        
      filter->a[1] = 1.f / (1.f + ROOT2 * c + c * c);
      filter->a[1] = 1.f / (1.f + (lpfloat_t)ROOT2 * c + c * c);
      filter->a[2] = filter->a[1] + filter->a[1];
      filter->a[3] = filter->a[1];
      filter->a[4] = 2.f * (1.f - c*c) * filter->a[1];
      filter->a[5] = (1.f - ROOT2 * c + c * c) * filter->a[1];
      filter->a[5] = (1.f - (lpfloat_t)ROOT2 * c + c * c) * filter->a[1];
    }

    t = in - filter->a[4] * filter->a[6] - filter->a[5] * filter->a[7];


@@ 1363,6 1364,14 @@ lpfloat_t fx_buttlp(lpbfilter_t * filter, lpfloat_t in) {
    return out;
}

/* Crossover distortion ported from the supercollider CrossoverDistortion ugen */
lpfloat_t fx_crossover(lpfloat_t val, lpfloat_t amount, lpfloat_t smooth, lpfloat_t fade) {
    lpfloat_t out;
    out = lpfabs(val) - amount;
    if(out < 0.f) out *= (1.f + (out*fade)) * smooth;
    if(val < 0.f) out *= -1.f;
    return out;
}

lpfloat_t fx_fold(lpfloat_t val, lpfloat_t * prev, lpfloat_t samplerate) {
    // Adapted from https://ccrma.stanford.edu/~jatin/ComplexNonlinearities/Wavefolder.html


@@ 1371,8 1380,8 @@ lpfloat_t fx_fold(lpfloat_t val, lpfloat_t * prev, lpfloat_t samplerate) {
    lpfloat_t z = tanhf(val) + (tanhf(*prev) * 0.9f);
    out = z + (-0.5f * sinf(2.f * (float)PI * val * (samplerate/2.f) / samplerate));
#else
    lpfloat_t z = tanh(val) + (tanh(*prev) * 0.9);
    out = z + (-0.5 * sin(2. * PI * val * (samplerate/2.) / samplerate));
    lpfloat_t z = tanh(val) + (tanh(*prev) * 0.9f);
    out = z + (-0.5f * sin(2.f * (lpfloat_t)PI * val * (samplerate/2.f) / samplerate));
#endif
    *prev = out;
    //return lpzapgremlins(out);


@@ 1997,7 2006,9 @@ void window_hanning(lpfloat_t* out, int length) {
/* create a window (0 to 1) */
lpbuffer_t * create_window(int name, size_t length) {
    lpbuffer_t* buf = LPBuffer.create(length, 1, DEFAULT_SAMPLERATE);
    if(name == WIN_SINE) {
    if(name == WIN_NONE) {
        memset(buf->data, 1.f, length * sizeof(lpfloat_t));
    } else if(name == WIN_SINE) {
        window_sine(buf->data, length);            
    } else if (name == WIN_SINEIN) {
        window_sinein(buf->data, length);            


@@ 2099,6 2110,14 @@ lpfloat_t lpfpow(lpfloat_t value, int exp) {
    return result;
}

lpfloat_t lpmstofreq(lpfloat_t ms) {
    return 1.f / (ms * 0.001f);
}

lpfloat_t lpstofreq(lpfloat_t seconds) {
    return 1.f / seconds;
}

/* FNV-1 hash implementation adapted from:
 * http://www.isthe.com/chongo/src/fnv/hash_32.c */
u_int32_t lphashstr(char * str) {

M libpippi/src/pippicore.h => libpippi/src/pippicore.h +11 -0
@@ 26,6 26,14 @@
/* ugen wrapper interface */
typedef struct ugen_t ugen_t;
struct ugen_t {
    // outlets / inlets include all params
    int num_outlets;
    int num_inlets;

    // audio-only inputs and outputs
    int num_outputs;
    int num_inputs;

    void * params;
    lpfloat_t (*get_output)(ugen_t * u, int index);
    void (*set_param)(ugen_t * u, int index, void * value);


@@ 187,6 195,7 @@ typedef struct lpfx_factory_t {
    lpfloat_t (*hpf1)(lpfloat_t x, lpfloat_t * y, lpfloat_t cutoff, lpfloat_t samplerate);
    void (*convolve)(lpbuffer_t * a, lpbuffer_t * b, lpbuffer_t * out);
    void (*norm)(lpbuffer_t * buf, lpfloat_t ceiling);
    lpfloat_t (*crossover)(lpfloat_t val, lpfloat_t amount, lpfloat_t smooth, lpfloat_t fade);
    lpfloat_t (*fold)(lpfloat_t val, lpfloat_t * prev, lpfloat_t samplerate);
    lpfloat_t (*limit)(lpfloat_t val, lpfloat_t * prev, lpfloat_t threshold, lpfloat_t release, lpbuffer_t * del);
    lpfloat_t (*crush)(lpfloat_t val, int bits);


@@ 243,6 252,8 @@ lpfloat_t lpfmax(lpfloat_t a, lpfloat_t b);
lpfloat_t lpfmin(lpfloat_t a, lpfloat_t b);
lpfloat_t lpfabs(lpfloat_t value);
lpfloat_t lpfpow(lpfloat_t value, int exp);
lpfloat_t lpmstofreq(lpfloat_t ms);
lpfloat_t lpstofreq(lpfloat_t seconds);
u_int32_t lphashstr(char * str);

lpfloat_t lpphaseinc(lpfloat_t freq, lpfloat_t samplerate);

M libpippi/src/ugens.sine.c => libpippi/src/ugens.sine.c +6 -0
@@ 48,6 48,12 @@ ugen_t * create_sine_ugen(void) {
    u->get_output = get_sine_ugen_output;
    u->set_param = set_sine_ugen_param;

    u->num_outlets = 3; // three param outlets
    u->num_inlets = 2; // two param inlets
                      
    u->num_outputs = 1; // one audio output
    u->num_inputs = 0; // no audio inputs

    return u;
}


M libpippi/src/ugens.tape.c => libpippi/src/ugens.tape.c +22 -1
@@ 52,7 52,21 @@ void set_tape_ugen_param(ugen_t * u, int index, void * value) {
                params->osc->current_frame = LPBuffer.create(1, buf->channels, buf->samplerate);
            }
            params->osc->range = buf->length-1;
            break;

        case UTAPEIN_PULSEWIDTH:
            v = (lpfloat_t *)value;
            params->osc->pulsewidth = *v;
            break;

        case UTAPEIN_START:
            v = (lpfloat_t *)value;
            params->osc->start = *v;
            break;

        case UTAPEIN_RANGE:
            v = (lpfloat_t *)value;
            params->osc->range = *v;
            break;

        default:


@@ 69,9 83,10 @@ lpfloat_t get_tape_ugen_output(ugen_t * u, int index) {
ugen_t * create_tape_ugen(void) {
    ugen_t * u;
    lpugentape_t * params;
    lpbuffer_t * frame = LPBuffer.create(1,1,1); // FIXME this leaks -- add req init params?

    params = (lpugentape_t *)LPMemoryPool.alloc(sizeof(lpugentape_t), 1);
    params->osc = LPTapeOsc.create(NULL, 1);
    params->osc = LPTapeOsc.create(frame);
    u = (ugen_t *)LPMemoryPool.alloc(sizeof(ugen_t), 1);

    u->params = (void *)params;


@@ 80,6 95,12 @@ ugen_t * create_tape_ugen(void) {
    u->get_output = get_tape_ugen_output;
    u->set_param = set_tape_ugen_param;

    u->num_outlets = 4; // three param outlets
    u->num_inlets = 7; // seven param inlets
                      
    u->num_outputs = 1; // one audio output
    u->num_inputs = 0; // no audio inputs

    return u;
}


M libpippi/src/ugens.tape.h => libpippi/src/ugens.tape.h +29 -3
@@ 9,18 9,44 @@
enum UgenTapeParams {
    UTAPEIN_SPEED,
    UTAPEIN_PHASE,
    UTAPEIN_BUF
    UTAPEIN_BUF,
    UTAPEIN_PULSEWIDTH,
    UTAPEIN_START,
    UTAPEIN_START_INCREMENT,
    UTAPEIN_RANGE,
};

/*
u_int32_t UgenTapeParamHashmap[7] = {
    LPHASHSTR("speed"),
    LPHASHSTR("phase"),
    LPHASHSTR("buf"),
    LPHASHSTR("pulsewidth"),
    LPHASHSTR("start"),
    LPHASHSTR("startinc"),
    LPHASHSTR("range"),
};
*/

enum UgenTapeOutputs {
    UTAPEOUT_MAIN,
    UTAPEOUT_SPEED,
    UTAPEOUT_PHASE
    UTAPEOUT_PHASE,
    UTAPEOUT_GATE,
};

/*
u_int32_t UgenTapeParamHashmap[4] = {
    LPHASHSTR("main"),
    LPHASHSTR("speed"),
    LPHASHSTR("phase"),
    LPHASHSTR("gate"),
};
*/

typedef struct lpugentape_t {
    lptapeosc_t * osc;
    lpfloat_t outputs[3];
    lpfloat_t outputs[4];
} lpugentape_t;

ugen_t * create_tape_ugen(void);

M pippi/fx.pxd => pippi/fx.pxd +24 -0
@@ 61,7 61,31 @@ ctypedef struct HBAP:

cdef extern from "pippicore.h":
    ctypedef double lpfloat_t
    ctypedef struct lpbuffer_t:
        size_t length;
        int samplerate;
        int channels;
        lpfloat_t phase
        size_t boundry
        size_t range
        size_t pos
        size_t onset
        int is_looping
        lpfloat_t data[]

    cdef lpfloat_t lpzapgremlins(lpfloat_t x)
    ctypedef struct lpfx_factory_t:
        lpfloat_t (*read_skewed_buffer)(lpfloat_t freq, lpbuffer_t * buf, lpfloat_t phase, lpfloat_t skew)
        lpfloat_t (*lpf1)(lpfloat_t x, lpfloat_t * y, lpfloat_t cutoff, lpfloat_t samplerate)
        lpfloat_t (*hpf1)(lpfloat_t x, lpfloat_t * y, lpfloat_t cutoff, lpfloat_t samplerate)
        void (*convolve)(lpbuffer_t * a, lpbuffer_t * b, lpbuffer_t * out)
        void (*norm)(lpbuffer_t * buf, lpfloat_t ceiling)
        lpfloat_t (*crossover)(lpfloat_t val, lpfloat_t amount, lpfloat_t smooth, lpfloat_t fade)
        lpfloat_t (*fold)(lpfloat_t val, lpfloat_t * prev, lpfloat_t samplerate)
        lpfloat_t (*limit)(lpfloat_t val, lpfloat_t * prev, lpfloat_t threshold, lpfloat_t release, lpbuffer_t * d)
        lpfloat_t (*crush)(lpfloat_t val, int bits)

    extern const lpfx_factory_t LPFX

cdef extern from "fx.softclip.h":
    ctypedef struct lpfxsoftclip_t:

M pippi/fx.pyx => pippi/fx.pyx +3 -11
@@ 155,9 155,8 @@ cpdef SoundBuffer softclip2(SoundBuffer snd):
@cython.cdivision(True)
cdef double[:,:] _crossover(double[:,:] snd, double[:,:] out, double[:] amount, double[:] smooth, double[:] fade):
    """ Crossover distortion ported from the supercollider CrossoverDistortion ugen """
    cdef int i=0, c=0
    cdef unsigned int framelength = len(snd)
    cdef int channels = snd.shape[1]
    cdef size_t i=0, framelength=len(snd)
    cdef int c=0, channels=snd.shape[1]
    cdef double s=0, pos=0, a=0, f=0, m=0

    for i in range(framelength):


@@ 167,14 166,7 @@ cdef double[:,:] _crossover(double[:,:] snd, double[:,:] out, double[:] amount, 
        f = _linear_pos(fade, pos)

        for c in range(channels):
            s = abs(snd[i,c]) - a
            if s < 0:
                s *= (1.0 + (s * f)) * m

            if snd[i,c] < 0:
                s *= -1

            out[i,c] = s
            out[i,c] = LPFX.crossover(snd[i,c], a, m, f)

    return out


M pippi/grains2.pxd => pippi/grains2.pxd +30 -31
@@ 1,5 1,6 @@
cdef extern from "pippicore.h":
    cdef enum Windows:
        WIN_NONE,
        WIN_SINE,
        WIN_SINEIN,
        WIN_SINEOUT,


@@ 82,10 83,15 @@ cdef extern from "pippicore.h":
        void (*dub)(lpbuffer_t *, lpbuffer_t *)
        void (*destroy)(lpbuffer_t *)

    ctypedef struct lpwindow_factory_t:
        lpbuffer_t * (*create)(int name, size_t length)
        lpbuffer_t * (*create_stack)(int numtables, size_t * onsets, size_t * lengths, ...)
        void (*destroy)(lpbuffer_t*)

    extern const lpparam_factory_t LPParam
    extern const lpbuffer_factory_t LPBuffer
    extern const lpringbuffer_factory_t LPRingBuffer

    extern const lpwindow_factory_t LPWindow

cdef extern from "oscs.tape.h":
    ctypedef struct lptapeosc_t:


@@ 94,7 100,6 @@ cdef extern from "oscs.tape.h":
        lpfloat_t pulsewidth
        lpfloat_t samplerate
        lpfloat_t start
        lpfloat_t start_increment
        lpfloat_t range
        lpbuffer_t * buf
        lpbuffer_t * current_frame


@@ 104,52 109,44 @@ cdef extern from "microsound.h":
    ctypedef struct lpgrain_t:
        size_t length
        int channels
        lpfloat_t pulsewidth

        size_t range
        size_t start
        size_t offset
        lpfloat_t samplerate;
        lpfloat_t pulsewidth 
        lpfloat_t grainlength
        lpfloat_t offset

        lpfloat_t phase_offset
        lpfloat_t phase
        lpfloat_t pan
        lpfloat_t amp
        lpfloat_t speed
        lpfloat_t skew

        int unused
        int gate

        lpbuffer_t * buf
        lpbuffer_t * window
        lptapeosc_t * src
        lptapeosc_t * win

    ctypedef struct lpformation_t:
        int num_active_grains
        size_t numlayers
        size_t grainlength
        lpgrain_t grains[512]
        int numgrains
        lpfloat_t grainlength
        lpfloat_t grainlength_maxjitter
        lpfloat_t grainlength_jitter

        size_t graininterval
        lpfloat_t graininterval_phase
        lpfloat_t graininterval_phase_inc
        lpfloat_t grid_maxjitter
        lpfloat_t grid_jitter

        lpfloat_t spread
        lpfloat_t speed
        lpfloat_t scrub
        lpfloat_t offset
        lpfloat_t skew
        lpfloat_t amp
        lpfloat_t pan
        lpfloat_t pulsewidth
        lpfloat_t pulsewidth 

        lpfloat_t pos
        lpbuffer_t * source
        lpbuffer_t * window
        lpbuffer_t * current_frame
        lpbuffer_t * rb

    ctypedef struct lpformation_factory_t:
        lpformation_t * (*create)(int, int, size_t, size_t, int, int, lpbuffer_t *)
        lpformation_t * (*create)(int numgrains, lpbuffer_t * src, lpbuffer_t * win);
        void (*process)(lpformation_t *)
        void (*destroy)(lpformation_t *)



@@ 161,17 158,19 @@ cdef class Cloud2:

    cdef lpformation_t * formation

    cdef double[:] grainlength
    cdef double[:] grid
    cdef double[:] phase

    """
    cdef double[:] position
    cdef double[:] amp
    cdef double[:] pulsewidth
    cdef double[:] grainlength
    cdef double[:] grainmaxjitter
    cdef double[:] grainjitter
    cdef double[:] gridmaxjitter
    cdef double[:] gridjitter
    cdef double[:] speed
    cdef double[:] spread
    cdef double[:] jitter
    cdef double[:] grid
    cdef bint gridincrement

    """
    cdef int[:] mask
    cdef bint has_mask
    """

M pippi/grains2.pyx => pippi/grains2.pyx +56 -36
@@ 57,6 57,9 @@ cdef lpbuffer_t * to_lpbuffer_window_from_soundbuffer(SoundBuffer window):
    return win

cdef int to_window_flag(str window_name=None):
    if window_name == 'none':
        return WIN_NONE

    if window_name == 'sine':
        return WIN_SINE



@@ 103,24 106,23 @@ cdef class Cloud2:
            object amp=1.0,
            object speed=1.0, 
            object spread=0.0, 
            object jitter=0.0, 
            object grainlength=0.2, 
            object pulsewidth=1.0,
            object grainmaxjitter=0.5, 
            object grainjitter=0.0, 
            object grainlength=0.1, 
            object gridmaxjitter=0.5, 
            object gridjitter=0.0, 
            object grid=None,
            object mask=None,
            object phase=None,
            double phase=0,
            int numgrains=2,
            unsigned int wtsize=4096,
            bint gridincrement=False,
        ):
        """ TODO:
            [ ] position
            [ ] amp 
            [ ] speed
            [ ] spread
            [ ] jitter
            [ ] mask
            [ ] numgrains modulation
        """

        # TODO: 
        # - mask / burst support
        # - set initial phase for formation
        cdef size_t i, c, sndlength
        cdef lpbuffer_t * win
        cdef int window_type


@@ 139,17 141,23 @@ cdef class Cloud2:

        else:
            window_type = to_window_flag(window)
            win = NULL

        if phase is None:
            phase = [0,1]
        self.phase = to_window(phase)
            win = LPWindow.create(window_type, 4096)

        if grid is None:
            self.grid = np.multiply(self.grainlength, 0.3)
            self.grid = np.multiply(self.grainlength, 0.5)
        else:
            self.grid = to_window(grid)

        self.amp = to_window(amp)
        self.speed = to_window(speed)
        self.spread = to_window(spread)
        self.pulsewidth = to_window(pulsewidth)
        self.gridmaxjitter = to_window(gridmaxjitter)
        self.gridjitter = to_window(gridjitter)
        self.grainmaxjitter = to_window(grainmaxjitter)
        self.grainjitter = to_window(grainjitter)
        self.gridincrement = gridincrement

        sndlength = <size_t>len(snd.frames)
        srcbuf = LPBuffer.create(sndlength, self.channels, self.samplerate)



@@ 157,39 165,51 @@ cdef class Cloud2:
            for c in range(self.channels):
                srcbuf.data[i * self.channels + c] = snd.frames[i, c]

        self.formation = LPFormation.create(
                window_type,
                numgrains, 
                0, 
                sndlength, 
                self.channels, 
                self.samplerate,
                win
        )

        LPRingBuffer.write(self.formation.rb, srcbuf)
        LPBuffer.destroy(srcbuf)
        self.formation = LPFormation.create(numgrains, srcbuf, win)

    def __dealloc__(self):
        if self.formation != NULL:
            LPFormation.destroy(self.formation)
            LPBuffer.destroy(self.formation.source)
            LPBuffer.destroy(self.formation.window)

    def play(self, double length):
        cdef size_t i, c, framelength
        cdef size_t i, c, framelength, incpos, increment
        cdef SoundBuffer out
        cdef double phase=0
        cdef double pos, amp

        increment = <size_t>(self.formation.grainlength * self.samplerate)
        framelength = <size_t>(length * self.samplerate)
        out = SoundBuffer(length=length, channels=self.channels, samplerate=self.samplerate)

        incpos = 0
        for i in range(framelength):
            phase = _linear_pos(self.phase, <double>i / framelength)
            self.formation.grainlength = <size_t>(_linear_pos(self.grainlength, phase) * self.samplerate)
            self.formation.graininterval = <size_t>(_linear_pos(self.grid, phase) * self.samplerate)
            pos = i / <double>framelength
            amp = _linear_pos(self.amp, pos)

            self.formation.pulsewidth = _linear_pos(self.pulsewidth, pos)
            self.formation.grainlength = _linear_pos(self.grainlength, pos)

            self.formation.grainlength_jitter = _linear_pos(self.grainjitter, pos)
            self.formation.grainlength_maxjitter = _linear_pos(self.grainmaxjitter, pos)
            self.formation.speed = _linear_pos(self.speed, pos)
            self.formation.spread = _linear_pos(self.spread, pos)
            self.formation.grid_jitter = _linear_pos(self.gridjitter, pos)
            self.formation.grid_maxjitter = _linear_pos(self.gridmaxjitter, pos)
            
            if not self.gridincrement:
                self.formation.offset = pos * length
            elif incpos >= increment:
                self.formation.offset += _linear_pos(self.grid, pos)
                while incpos >= increment:
                    incpos -= increment
                increment = <size_t>(self.formation.grainlength * self.samplerate)

            LPFormation.process(self.formation)
            for c in range(self.channels):
                out.frames[i, c] = self.formation.current_frame.data[c]
                out.frames[i, c] = self.formation.current_frame.data[c] * amp

            incpos += 1

        return out


M pippi/soundbuffer.pyx => pippi/soundbuffer.pyx +2 -2
@@ 715,8 715,8 @@ cdef class SoundBuffer:
    def cloud(SoundBuffer self, double length=-1, *args, **kwargs):
        """ Create a new Cloud from this SoundBuffer
        """
        #return grains2.Cloud2(self, *args, **kwargs).play(length)
        return grains.Cloud(self, *args, **kwargs).play(length)
        return grains2.Cloud2(self, *args, **kwargs).play(length)
        #return grains.Cloud(self, *args, **kwargs).play(length)

    def copy(self):
        """ Return a new copy of this SoundBuffer.

M tests/test_graincloud.py => tests/test_graincloud.py +69 -6
@@ 22,8 22,7 @@ class TestCloud(TestCase):
        framelength = int(length * sound.samplerate)

        out = cloud.play(length)
        out = fx.compressor(out*4, -15, 15)
        out = fx.norm(out, 0.5)
        out = fx.norm(out, 1)

        out.write('tests/renders/graincloud_libpippi_unmodulated.wav')



@@ 33,26 32,90 @@ class TestCloud(TestCase):
        snd = dsp.read('tests/sounds/living.wav')
        grainlength = shapes.win('sine', dsp.MS*10, 0.2)
        out = snd.cloud(snd.dur*2, grainlength=grainlength)
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_grainlength_modulated.wav')

    def test_user_window(self):
        # Alix Dobkin
        snd = dsp.read('tests/sounds/living.wav')
        win = dsp.win('pluckout')
        grainlength = shapes.win('sine', dsp.MS*10, 0.2)
        out = snd.cloud(snd.dur*2, window=win, grainlength=grainlength)

        length = 12

        # right half of a sharply tapered hann window, basically
        win = dsp.win('pluckout').taper(dsp.randint(10, 100))

        # empty buffer 2x the length of the sample
        out = dsp.buffer(length=length, channels=snd.channels, samplerate=snd.samplerate)

        # four layers of grains
        for _ in range(4):
            # grain playback speed
            speed = shapes.win('sine', dsp.rand(0.125, 0.5), dsp.rand(1, 2))

            amp = shapes.win('sine', 0.3, 1) 

            # stereo spread
            spread = shapes.win('sine', 0, 1) 

            # start increment in seconds
            grid = shapes.win('sine', -1, 1, length=dsp.rand(1, 10))

            # length of the grain in seconds
            grainlength = shapes.win('sine', dsp.MS*1, 0.4, length=dsp.rand(1, 10))

            gridjitter = shapes.win('sine', 0, 1)
            grainjitter = shapes.win('sine', 0, 1)

            # pulsewidth of the grain window between 0 & 1
            pulsewidth = shapes.win('sine', 0.5, 2)

            # render the layer
            layer = snd.cloud(length, 
                amp=amp,
                window=win, 
                grainlength=grainlength, 
                numgrains=2, 
                speed=speed, 
                pulsewidth=pulsewidth, 
                spread=spread,
                gridincrement=True,
                grainmaxjitter=dsp.rand(0.01,10),
                grainjitter=grainjitter,
                gridmaxjitter=dsp.rand(0.01,1),
                gridjitter=gridjitter,
            )

            # and dub it into the output buffer
            out.dub(layer)

        # squish
        out = fx.compressor(out*8, -15, 15)
        out = fx.norm(out, 1)

        # done
        out.write('tests/renders/graincloud_libpippi_user_window.wav')

    def test_phase_modulation(self):
        snd = dsp.read('tests/sounds/living.wav')
        phase = [1,0.5,1,0,0.5]
        phase = 3
        out = snd.cloud(snd.dur*2, grainlength=0.1, phase=phase)
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_phase_modulated.wav')

    def test_phase_unmodulated(self):
        snd = dsp.read('tests/sounds/living.wav')
        out = snd.cloud(snd.dur*2, grainlength=0.1)
        out = snd.cloud(snd.dur, grainlength=0.1)
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_phase_unmodulated.wav')

    def test_speed_modulated(self):
        snd = dsp.read('tests/sounds/living.wav')
        speed = shapes.win('sine', 0.5, 2)
        out = snd.cloud(snd.dur, grainlength=0.1, speed=speed)
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_speed_modulated.wav')


    """
    def test_libpippi_pulsed_graincloud(self):