shithub: leaf

ref: fe27383f5704ddc2d8ed4fad975fe937bca129c1
dir: /leaf/Src/leaf-oscillators.c/

View raw version
/*==============================================================================
 leaf-oscillators.c
 Created: 20 Jan 2017 12:00:58pm
 Author:  Michael R Mulshine
 ==============================================================================*/

#if _WIN32 || _WIN64

#include "..\Inc\leaf-oscillators.h"
#include "..\leaf.h"

#else

#include "../Inc/leaf-oscillators.h"
#include "../leaf.h"

#endif

// WaveTable
void    tTable_init(tTable* const cy, float* waveTable, int size, LEAF* const leaf)
{
    tTable_initToPool(cy, waveTable, size, &leaf->mempool);
}

void    tTable_initToPool(tTable* const cy, float* waveTable, int size, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tTable* c = *cy = (_tTable*)mpool_alloc(sizeof(_tTable), m);
    c->mempool = m;
    
    c->waveTable = waveTable;
    c->size = size;
    c->inc = 0.0f;
    c->phase = 0.0f;
}

void    tTable_free(tTable* const cy)
{
    _tTable* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

void     tTable_setFreq(tTable* const cy, float freq)
{
    _tTable* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    if (!isfinite(freq)) return;
    
    c->freq = freq;
    c->inc = freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}

float   tTable_tick(tTable* const cy)
{
    _tTable* c = *cy;
    float temp;
    int intPart;
    float fracPart;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    // Wavetable synthesis
    
    temp = c->size * c->phase;
    intPart = (int)temp;
    fracPart = temp - (float)intPart;
    samp0 = c->waveTable[intPart];
    if (++intPart >= c->size) intPart = 0;
    samp1 = c->waveTable[intPart];
    
    return (samp0 + (samp1 - samp0) * fracPart);
}

void     tTableSampleRateChanged(tTable* const cy)
{
    _tTable* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}

void tWavetable_init(tWavetable* const cy, const float* table, int size, float maxFreq, LEAF* const leaf)
{
    tWavetable_initToPool(cy, table, size, maxFreq, &leaf->mempool);
}

void tWavetable_initToPool(tWavetable* const cy, const float* table, int size, float maxFreq, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tWavetable* c = *cy = (_tWavetable*) mpool_alloc(sizeof(_tWavetable), m);
    c->mempool = m;
    
    // Determine base frequency
    c->baseFreq = m->leaf->sampleRate / (float) size;
    c->invBaseFreq = 1.0f / c->baseFreq;
    
    // Determine how many tables we need
    // Assume we need at least 2, the fundamental + one to account for setting extra anti aliasing
    c->numTables = 2;
    float f = c->baseFreq;
    while (f < maxFreq)
    {
        c->numTables++;
        f *= 2.0f; // pass this multiplier in to set spacing of tables? would need to change setFreq too
    }
    
    c->size = size;

    // Allocate memory for the tables
    c->tables = (float**) mpool_alloc(sizeof(float*) * c->numTables, m);
    for (int t = 0; t < c->numTables; ++t)
    {
        c->tables[t] = (float*) mpool_alloc(sizeof(float) * c->size, m);
    }
    
    // Copy table
    for (int i = 0; i < c->size; ++i)
    {
        c->tables[0][i] = table[i];
    }
    
    // Make bandlimited copies
    f = m->leaf->sampleRate * 0.25; //start at half nyquist
    // Not worth going over order 8 I think, and even 8 is only marginally better than 4.
    tButterworth_initToPool(&c->bl, 8, -1.0f, f, mp);
    for (int t = 1; t < c->numTables; ++t)
    {
        tButterworth_setF2(&c->bl, f);
        // Do several passes here to prevent errors at the beginning of the waveform
        // Not sure how many passes to do, seem to need more as the filter cutoff goes down
        // 12 might be excessive but seems to work for now.
        for (int p = 0; p < 12; ++p)
        {
            for (int i = 0; i < c->size; ++i)
            {
                c->tables[t][i] = tButterworth_tick(&c->bl, c->tables[t-1][i]);
            }
        }
        f *= 0.5f; //halve the cutoff for next pass
    }
    tButterworth_free(&c->bl);


    c->inc = 0.0f;
    c->phase = 0.0f;
    c->aa = 0.5f;
    
    tWavetable_setFreq(cy, 220);
}

void tWavetable_free(tWavetable* const cy)
{
    _tWavetable* c = *cy;
    
    for (int t = 0; t < c->numTables; ++t)
    {
        mpool_free((char*)c->tables[t], c->mempool);
    }
    mpool_free((char*)c->tables, c->mempool);
    mpool_free((char*)c, c->mempool);
}

float tWavetable_tick(tWavetable* const cy)
{
    _tWavetable* c = *cy;
    
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    // Wavetable synthesis
    temp = c->size * c->phase;
    
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = c->tables[c->oct][idx];
    if (++idx >= c->size) idx = 0;
    samp1 = c->tables[c->oct][idx];
    
    float oct0 = (samp0 + (samp1 - samp0) * frac);
    
    idx = (int)temp;
    samp0 = c->tables[c->oct+1][idx];
    if (++idx >= c->size) idx = 0;
    samp1 = c->tables[c->oct+1][idx];
    
    float oct1 = (samp0 + (samp1 - samp0) * frac);
    
    return oct0 + (oct1 - oct0) * c->w;
}

void tWavetable_setFreq(tWavetable* const cy, float freq)
{
    _tWavetable* c = *cy;
    
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
    
    // abs for negative frequencies
    c->w = fabsf(c->freq * c->invBaseFreq);
    
    // Probably ok to use a log2 approx here; won't effect tuning at all, just crossfading between octave tables
    c->w = log2f_approx(c->w) + c->aa; // adding an offset here will shift our table selection upward, reducing aliasing but lower high freq fidelity. +1.0f should remove all aliasing
    if (c->w < 0.0f) c->w = 0.0f; // If c->w is < 0.0f, then freq is less than our base freq
    c->oct = (int)c->w;
    c->w -= c->oct;
    
    // When out of range of our tables, this will prevent a crash.
    // Also causes a blip but fine since we're above maxFreq at this point.
    if (c->oct >= c->numTables - 1) c->oct = c->numTables - 2;
}

void tWavetable_setAntiAliasing(tWavetable* const cy, float aa)
{
    _tWavetable* c = *cy;
    
    c->aa = aa;
    tWavetable_setFreq(cy, c->freq);
}

void tCompactWavetable_init(tCompactWavetable* const cy, const float* table, int size, float maxFreq, LEAF* const leaf)
{
    tCompactWavetable_initToPool(cy, table, size, maxFreq, &leaf->mempool);
}

void tCompactWavetable_initToPool(tCompactWavetable* const cy, const float* table, int size, float maxFreq, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tCompactWavetable* c = *cy = (_tCompactWavetable*) mpool_alloc(sizeof(_tCompactWavetable), m);
    c->mempool = m;
    
    // Determine base frequency
    c->baseFreq = m->leaf->sampleRate / (float) size;
    c->invBaseFreq = 1.0f / c->baseFreq;
    
    // Determine how many tables we need
    c->numTables = 2;
    float f = c->baseFreq;
    while (f < maxFreq)
    {
        c->numTables++;
        f *= 2.0f; // pass this multiplier in to set spacing of tables?
    }
        
    // Allocate memory for the tables
    c->tables = (float**) mpool_alloc(sizeof(float*) * c->numTables, c->mempool);
    c->sizes = (int*) mpool_alloc(sizeof(int) * c->numTables, c->mempool);
    int n = size;
    for (int t = 0; t < c->numTables; ++t)
    {
        c->sizes[t] = n;
        c->tables[t] = (float*) mpool_alloc(sizeof(float) * c->sizes[t], m);
        n = c->sizes[t] / 2;
    }
    
    // Copy table
    for (int i = 0; i < c->sizes[0]; ++i)
    {
        c->tables[0][i] = table[i];
    }
    
    // Make bandlimited copies
    // Not worth going over order 8 I think, and even 8 is only marginally better than 4.
    tButterworth_initToPool(&c->bl, 8, -1.0f, m->leaf->sampleRate * 0.25f, mp);
    tOversampler_initToPool(&c->ds, 2, 1, mp);
    for (int t = 1; t < c->numTables; ++t)
    {
        // Similar to tWavetable, doing multiple passes here helps, but not sure what number is optimal
        for (int p = 0; p < 12; ++p)
        {
            for (int i = 0; i < c->sizes[t]; ++i)
            {
                c->dsBuffer[0] = tButterworth_tick(&c->bl, c->tables[t-1][i*2]);
                c->dsBuffer[1] = tButterworth_tick(&c->bl, c->tables[t-1][(i*2)+1]);
                c->tables[t][i] = tOversampler_downsample(&c->ds, c->dsBuffer);
            }
        }
    }
    tOversampler_free(&c->ds);
    tButterworth_free(&c->bl);

    c->inc = 0.0f;
    c->phase = 0.0f;
    c->aa = 0.5f;
    
    tCompactWavetable_setFreq(cy, 220);
}

void    tCompactWavetable_free(tCompactWavetable* const cy)
{
    _tCompactWavetable* c = *cy;
    
    for (int t = 0; t < c->numTables; ++t)
    {
        mpool_free((char*)c->tables[t], c->mempool);
    }
    mpool_free((char*)c->tables, c->mempool);
    mpool_free((char*)c->sizes, c->mempool);
    mpool_free((char*)c, c->mempool);
}

float   tCompactWavetable_tick(tCompactWavetable* const cy)
{
    _tCompactWavetable* c = *cy;
    
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    // Wavetable synthesis
    temp = c->sizes[c->oct] * c->phase;
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = c->tables[c->oct][idx];
    if (++idx >= c->sizes[c->oct]) idx = 0;
    samp1 = c->tables[c->oct][idx];
    
    float oct0 = (samp0 + (samp1 - samp0) * frac);
    
    temp = c->sizes[c->oct+1] * c->phase;
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = c->tables[c->oct+1][idx];
    if (++idx >= c->sizes[c->oct+1]) idx = 0;
    samp1 = c->tables[c->oct+1][idx];
    
    float oct1 = (samp0 + (samp1 - samp0) * frac);
    
    return oct0 + (oct1 - oct0) * c->w;
}

void    tCompactWavetable_setFreq(tCompactWavetable* const cy, float freq)
{
    _tCompactWavetable* c = *cy;
    
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
    
    // abs for negative frequencies
    c->w = fabsf(c->freq * c->invBaseFreq);
    
    // Probably ok to use a log2 approx here; won't effect tuning at all, just crossfading between octave tables
    c->w = log2f_approx(c->w) + c->aa;//+ LEAF_SQRT2 - 1.0f; adding an offset here will shift our table selection upward, reducing aliasing but lower high freq fidelity. +1.0f should remove all aliasing
    if (c->w < 0.0f) c->w = 0.0f; // If c->w is < 0.0f, then freq is less than our base freq
    c->oct = (int)c->w;
    c->w -= c->oct;
    if (c->oct >= c->numTables - 1) c->oct = c->numTables - 2;
}

void tCompactWavetable_setAntiAliasing(tCompactWavetable* const cy, float aa)
{
    _tCompactWavetable* c = *cy;
    
    c->aa = aa;
    tCompactWavetable_setFreq(cy, c->freq);
}

#if LEAF_INCLUDE_SINE_TABLE
// Cycle
void    tCycle_init(tCycle* const cy, LEAF* const leaf)
{
    tCycle_initToPool(cy, &leaf->mempool);
}

void    tCycle_initToPool   (tCycle* const cy, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tCycle* c = *cy = (_tCycle*) mpool_alloc(sizeof(_tCycle), m);
    c->mempool = m;
    
    c->inc      =  0.0f;
    c->phase    =  0.0f;
}

void    tCycle_free (tCycle* const cy)
{
    _tCycle* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

void     tCycle_setFreq(tCycle* const cy, float freq)
{
    _tCycle* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    if (!isfinite(freq)) return;
    
    c->freq  = freq;

    c->inc = freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}

//need to check bounds and wrap table properly to allow through-zero FM
float   tCycle_tick(tCycle* const cy)
{
    _tCycle* c = *cy;
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;

    // Wavetable synthesis

    temp = SINE_TABLE_SIZE * c->phase;
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = __leaf_table_sinewave[idx];
    if (++idx >= SINE_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_sinewave[idx];

    return (samp0 + (samp1 - samp0) * frac);
}

void     tCycleSampleRateChanged (tCycle* const cy)
{
    _tCycle* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}
#endif // LEAF_INCLUDE_SINE_TABLE

#if LEAF_INCLUDE_TRIANGLE_TABLE
//========================================================================
/* Triangle */
void   tTriangle_init(tTriangle* const cy, LEAF* const leaf)
{
    tTriangle_initToPool(cy, &leaf->mempool);
}

void    tTriangle_initToPool    (tTriangle* const cy, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tTriangle* c = *cy = (_tTriangle*) mpool_alloc(sizeof(_tTriangle), m);
    c->mempool = m;
    
    c->inc      =  0.0f;
    c->phase    =  0.0f;
    tTriangle_setFreq(cy, 220);
}

void    tTriangle_free  (tTriangle* const cy)
{
    _tTriangle* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

void tTriangle_setFreq(tTriangle* const cy, float freq)
{
    _tTriangle* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
    
    // abs for negative frequencies
    c->w = fabsf(c->freq * (TRI_TABLE_SIZE * leaf->invSampleRate));
    
    c->w = log2f_approx(c->w);//+ LEAF_SQRT2 - 1.0f; adding an offset here will shift our table selection upward, reducing aliasing but lower high freq fidelity. +1.0f should remove all aliasing
    if (c->w < 0.0f) c->w = 0.0f;
    c->oct = (int)c->w;
    c->w -= c->oct;
    if (c->oct >= 10) c->oct = 9;
}


float   tTriangle_tick(tTriangle* const cy)
{
    _tTriangle* c = *cy;
    
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;

    // Wavetable synthesis
    temp = TRI_TABLE_SIZE * c->phase;
    
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = __leaf_table_triangle[c->oct][idx];
    if (++idx >= TRI_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_triangle[c->oct][idx];
    
    float oct0 = (samp0 + (samp1 - samp0) * frac);
    
    idx = (int)temp;
    samp0 = __leaf_table_triangle[c->oct+1][idx];
    if (++idx >= TRI_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_triangle[c->oct+1][idx];
    
    float oct1 = (samp0 + (samp1 - samp0) * frac);
    
    return oct0 + (oct1 - oct0) * c->w;
}

void     tTriangleSampleRateChanged (tTriangle* const cy)
{
    _tTriangle* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}
#endif // LEAF_INCLUDE_TRIANGLE_TABLE

#if LEAF_INCLUDE_SQUARE_TABLE
//========================================================================
/* Square */
void   tSquare_init(tSquare* const cy, LEAF* const leaf)
{
    tSquare_initToPool(cy, &leaf->mempool);
}

void    tSquare_initToPool  (tSquare* const cy, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tSquare* c = *cy = (_tSquare*) mpool_alloc(sizeof(_tSquare), m);
    c->mempool = m;
    
    c->inc      =  0.0f;
    c->phase    =  0.0f;
    tSquare_setFreq(cy, 220);
}

void    tSquare_free (tSquare* const cy)
{
    _tSquare* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

void    tSquare_setFreq(tSquare* const cy, float freq)
{
    _tSquare* c = *cy;
    LEAF* leaf = c->mempool->leaf;

    c->freq  = freq;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
    
    // abs for negative frequencies
    c->w = fabsf(c->freq * (SQR_TABLE_SIZE * leaf->invSampleRate));
    
    c->w = log2f_approx(c->w);//+ LEAF_SQRT2 - 1.0f; adding an offset here will shift our table selection upward, reducing aliasing but lower high freq fidelity. +1.0f should remove all aliasing
    if (c->w < 0.0f) c->w = 0.0f;
    c->oct = (int)c->w;
    c->w -= c->oct;
    if (c->oct >= 10) c->oct = 9;
}

float   tSquare_tick(tSquare* const cy)
{
    _tSquare* c = *cy;
    
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;

    // Wavetable synthesis
    temp = SQR_TABLE_SIZE * c->phase;
    
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = __leaf_table_squarewave[c->oct][idx];
    if (++idx >= SQR_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_squarewave[c->oct][idx];
    
    float oct0 = (samp0 + (samp1 - samp0) * frac);
    
    idx = (int)temp;
    samp0 = __leaf_table_squarewave[c->oct+1][idx];
    if (++idx >= SQR_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_squarewave[c->oct+1][idx];
    
    float oct1 = (samp0 + (samp1 - samp0) * frac);
    
    return oct0 + (oct1 - oct0) * c->w;
}

void     tSquareSampleRateChanged (tSquare* const cy)
{
    _tSquare* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}
#endif // LEAF_INCLUDE_SQUARE_TABLE

#if LEAF_INCLUDE_SAWTOOTH_TABLE
//=====================================================================
// Sawtooth
void    tSawtooth_init(tSawtooth* const cy, LEAF* const leaf)
{
    tSawtooth_initToPool(cy, &leaf->mempool);
}

void    tSawtooth_initToPool    (tSawtooth* const cy, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tSawtooth* c = *cy = (_tSawtooth*) mpool_alloc(sizeof(_tSawtooth), m);
    c->mempool = m;
    
    c->inc      = 0.0f;
    c->phase    = 0.0f;
    tSawtooth_setFreq(cy, 220);
}

void    tSawtooth_free (tSawtooth* const cy)
{
    _tSawtooth* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

void    tSawtooth_setFreq(tSawtooth* const cy, float freq)
{
    _tSawtooth* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
    
    // abs for negative frequencies
    c->w = fabsf(c->freq * (SAW_TABLE_SIZE * leaf->invSampleRate));
    
    c->w = log2f_approx(c->w);//+ LEAF_SQRT2 - 1.0f; adding an offset here will shift our table selection upward, reducing aliasing but lower high freq fidelity. +1.0f should remove all aliasing
    if (c->w < 0.0f) c->w = 0.0f; // If c->w is < 0.0f, then freq is less than our base freq
    c->oct = (int)c->w;
    c->w -= c->oct;
    if (c->oct >= 10) c->oct = 9;
}

float   tSawtooth_tick(tSawtooth* const cy)
{
    _tSawtooth* c = *cy;
    
    float temp;
    int idx;
    float frac;
    float samp0;
    float samp1;
    
    // Phasor increment
    c->phase += c->inc;
    while (c->phase >= 1.0f) c->phase -= 1.0f;
    while (c->phase < 0.0f) c->phase += 1.0f;
    
    // Wavetable synthesis
    temp = SAW_TABLE_SIZE * c->phase;
    
    idx = (int)temp;
    frac = temp - (float)idx;
    samp0 = __leaf_table_sawtooth[c->oct][idx];
    if (++idx >= SAW_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_sawtooth[c->oct][idx];
    
    float oct0 = (samp0 + (samp1 - samp0) * frac);
    
    idx = (int)temp;
    samp0 = __leaf_table_sawtooth[c->oct+1][idx];
    if (++idx >= SAW_TABLE_SIZE) idx = 0;
    samp1 = __leaf_table_sawtooth[c->oct+1][idx];
    
    float oct1 = (samp0 + (samp1 - samp0) * frac);
    
    return oct0 + (oct1 - oct0) * c->w;
}

void     tSawtoothSampleRateChanged (tSawtooth* const cy)
{
    _tSawtooth* c = *cy;
    LEAF* leaf = c->mempool->leaf;
    
    c->inc = c->freq * leaf->invSampleRate;
    c->inc -= (int)c->inc;
}
#endif // LEAF_INCLUDE_SAWTOOTH_TABLE

//==============================================================================

/* tTri: Anti-aliased Triangle waveform. */
void    tPBTriangle_init          (tPBTriangle* const osc, LEAF* const leaf)
{
    tPBTriangle_initToPool(osc, &leaf->mempool);
}

void    tPBTriangle_initToPool    (tPBTriangle* const osc, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tPBTriangle* c = *osc = (_tPBTriangle*) mpool_alloc(sizeof(_tPBTriangle), m);
    c->mempool = m;

    c->inc      =  0.0f;
    c->phase    =  0.0f;
    c->skew     =  0.5f;
    c->lastOut  =  0.0f;
}

void    tPBTriangle_free (tPBTriangle* const cy)
{
    _tPBTriangle* c = *cy;
    
    mpool_free((char*)c, c->mempool);
}

float   tPBTriangle_tick          (tPBTriangle* const osc)
{
    _tPBTriangle* c = *osc;
    
    float out;
    float skew;
    
    if (c->phase < c->skew)
    {
        out = 1.0f;
        skew = (1.0f - c->skew) * 2.0f;
    }
    else
    {
        out = -1.0f;
        skew = c->skew * 2.0f;
    }
    
    out += LEAF_poly_blep(c->phase, c->inc);
    out -= LEAF_poly_blep(fmodf(c->phase + (1.0f - c->skew), 1.0f), c->inc);
    
    out = (skew * c->inc * out) + ((1 - c->inc) * c->lastOut);
    c->lastOut = out;
    
    c->phase += c->inc - (int)c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    return out;
}

void    tPBTriangle_setFreq       (tPBTriangle* const osc, float freq)
{
    _tPBTriangle* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    c->inc = freq * leaf->invSampleRate;
}

void    tPBTriangle_setSkew       (tPBTriangle* const osc, float skew)
{
    _tPBTriangle* c = *osc;
    c->skew = (skew + 1.0f) * 0.5f;
}


//==============================================================================

/* tPulse: Anti-aliased pulse waveform. */
void    tPBPulse_init        (tPBPulse* const osc, LEAF* const leaf)
{
    tPBPulse_initToPool(osc, &leaf->mempool);
}

void    tPBPulse_initToPool  (tPBPulse* const osc, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tPBPulse* c = *osc = (_tPBPulse*) mpool_alloc(sizeof(_tPBPulse), m);
    c->mempool = m;
    
    c->inc      =  0.0f;
    c->phase    =  0.0f;
    c->width     =  0.5f;
}

void    tPBPulse_free (tPBPulse* const osc)
{
    _tPBPulse* c = *osc;
    
    mpool_free((char*)c, c->mempool);
}

float   tPBPulse_tick        (tPBPulse* const osc)
{
    _tPBPulse* c = *osc;
    
    float out;
    if (c->phase < c->width) out = 1.0f;
    else out = -1.0f;
    out += LEAF_poly_blep(c->phase, c->inc);
    out -= LEAF_poly_blep(fmodf(c->phase + (1.0f - c->width), 1.0f), c->inc);
    
    c->phase += c->inc - (int)c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    return out;
}

void    tPBPulse_setFreq     (tPBPulse* const osc, float freq)
{
    _tPBPulse* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    c->inc = freq * leaf->invSampleRate;
}

void    tPBPulse_setWidth    (tPBPulse* const osc, float width)
{
    _tPBPulse* c = *osc;
    c->width = width;
}


//==============================================================================

/* tSawtooth: Anti-aliased Sawtooth waveform. */
void    tPBSaw_init          (tPBSaw* const osc, LEAF* const leaf)
{
    tPBSaw_initToPool(osc, &leaf->mempool);
}

void    tPBSaw_initToPool    (tPBSaw* const osc, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tPBSaw* c = *osc = (_tPBSaw*) mpool_alloc(sizeof(_tPBSaw), m);
    c->mempool = m;
    
    c->inc      =  0.0f;
    c->phase    =  0.0f;
}

void    tPBSaw_free  (tPBSaw* const osc)
{
    _tPBSaw* c = *osc;
    
    mpool_free((char*)c, c->mempool);
}

float   tPBSaw_tick          (tPBSaw* const osc)
{
    _tPBSaw* c = *osc;
    
    float out = (c->phase * 2.0f) - 1.0f;
    out -= LEAF_poly_blep(c->phase, c->inc);
    
    c->phase += c->inc - (int)c->inc;
    if (c->phase >= 1.0f) c->phase -= 1.0f;
    if (c->phase < 0.0f) c->phase += 1.0f;
    
    return out;
}

void    tPBSaw_setFreq       (tPBSaw* const osc, float freq)
{
    _tPBSaw* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    c->freq  = freq;
    
    c->inc = freq * leaf->invSampleRate;
}

//========================================================================
/* Phasor */
void     tPhasorSampleRateChanged (tPhasor* const ph)
{
    _tPhasor* p = *ph;
    LEAF* leaf = p->mempool->leaf;
    
    p->inc = p->freq * leaf->invSampleRate;
};

void    tPhasor_init(tPhasor* const ph, LEAF* const leaf)
{
    tPhasor_initToPool(ph, &leaf->mempool);
}

void    tPhasor_initToPool  (tPhasor* const ph, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tPhasor* p = *ph = (_tPhasor*) mpool_alloc(sizeof(_tPhasor), m);
    p->mempool = m;
    
    p->phase = 0.0f;
    p->inc = 0.0f;
    p->phaseDidReset = 0;
}

void    tPhasor_free (tPhasor* const ph)
{
    _tPhasor* p = *ph;
    
    mpool_free((char*)p, p->mempool);
}

void    tPhasor_setFreq(tPhasor* const ph, float freq)
{
    _tPhasor* p = *ph;
    LEAF* leaf = p->mempool->leaf;

    p->freq  = freq;
    
    p->inc = freq * leaf->invSampleRate;
    p->inc -= (int)p->inc;
}

float   tPhasor_tick(tPhasor* const ph)
{
    _tPhasor* p = *ph;
    
    p->phase += p->inc;

    p->phaseDidReset = 0;
    if (p->phase >= 1.0f)
    {
        p->phaseDidReset = 1;
        p->phase -= 1.0f;
    }
    else if (p->phase < 0.0f)
    {
        p->phaseDidReset = 1;
        p->phase += 1.0f;
    }
    
    return p->phase;
}

/* Noise */
void    tNoise_init(tNoise* const ns, NoiseType type, LEAF* const leaf)
{
    tNoise_initToPool(ns, type, &leaf->mempool);
}

void    tNoise_initToPool   (tNoise* const ns, NoiseType type, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tNoise* n = *ns = (_tNoise*) mpool_alloc(sizeof(_tNoise), m);
    n->mempool = m;
    LEAF* leaf = n->mempool->leaf;
    
    n->type = type;
    n->rand = leaf->random;
}

void    tNoise_free (tNoise* const ns)
{
    _tNoise* n = *ns;
    
    mpool_free((char*)n, n->mempool);
}

float   tNoise_tick(tNoise* const ns)
{
    _tNoise* n = *ns;
    
    float rand = (n->rand() * 2.0f) - 1.0f;
    
    if (n->type == PinkNoise)
    {
        float tmp;
        n->pinkb0 = 0.99765f * n->pinkb0 + rand * 0.0990460f;
        n->pinkb1 = 0.96300f * n->pinkb1 + rand * 0.2965164f;
        n->pinkb2 = 0.57000f * n->pinkb2 + rand * 1.0526913f;
        tmp = n->pinkb0 + n->pinkb1 + n->pinkb2 + rand * 0.1848f;
        return (tmp * 0.05f);
    }
    else // WhiteNoise
    {
        return rand;
    }
}

//=================================================================================
/* Neuron */

void tNeuronSampleRateChanged(tNeuron* nr)
{
    
}

void tNeuron_init(tNeuron* const nr, LEAF* const leaf)
{
    tNeuron_initToPool(nr, &leaf->mempool);
}

void tNeuron_initToPool  (tNeuron* const nr, tMempool* const mp)
{
    _tMempool* m = *mp;
    _tNeuron* n = *nr = (_tNeuron*) mpool_alloc(sizeof(_tNeuron), m);
    n->mempool = m;
    
    tPoleZero_initToPool(&n->f, mp);
    
    tPoleZero_setBlockZero(&n->f, 0.99f);
    
    n->timeStep = 1.0f / 50.0f;
    
    n->current = 0.0f; // 100.0f for sound
    n->voltage = 0.0f;
    
    n->mode = NeuronNormal;
    
    n->P[0] = 0.0f;
    n->P[1] = 0.0f;
    n->P[2] = 1.0f;
    
    n->V[0] = -12.0f;
    n->V[1] = 115.0f;
    n->V[2] = 10.613f;
    
    n->gK = 36.0f;
    n->gN = 120.0f;
    n->gL = 0.3f;
    n->C = 1.0f;
    
    n->rate[2] = n->gL/n->C;
}

void    tNeuron_free (tNeuron* const nr)
{
    _tNeuron* n = *nr;
    
    tPoleZero_free(&n->f);
    mpool_free((char*)n, n->mempool);
}

void   tNeuron_reset(tNeuron* const nr)
{
    _tNeuron* n = *nr;
    
    tPoleZero_setBlockZero(&n->f, 0.99f);
    
    n->timeStep = 1.0f / 50.0f;
    
    n->current = 0.0f; // 100.0f for sound
    n->voltage = 0.0f;
    
    n->mode = NeuronNormal;
    
    n->P[0] = 0.0f;
    n->P[1] = 0.0f;
    n->P[2] = 1.0f;
    
    n->V[0] = -12.0f;
    n->V[1] = 115.0f;
    n->V[2] = 10.613f;
    
    n->gK = 36.0f;
    n->gN = 120.0f;
    n->gL = 0.3f;
    n->C = 1.0f;
    
    n->rate[2] = n->gL/n->C;
}

void tNeuron_setV1(tNeuron* const nr, float V1)
{
    _tNeuron* n = *nr;
    n->V[0] = V1;
}


void tNeuron_setV2(tNeuron* const nr, float V2)
{
    _tNeuron* n = *nr;
    n->V[1] = V2;
}

void tNeuron_setV3(tNeuron* const nr, float V3)
{
    _tNeuron* n = *nr;
    n->V[2] = V3;
}

void tNeuron_setTimeStep(tNeuron* const nr, float timeStep)
{
    _tNeuron* n = *nr;
    n->timeStep = timeStep;
}

void tNeuron_setK(tNeuron* const nr, float K)
{
    _tNeuron* n = *nr;
    n->gK = K;
}

void tNeuron_setL(tNeuron* const nr, float L)
{
    _tNeuron* n = *nr;
    n->gL = L;
    n->rate[2] = n->gL/n->C;
}

void tNeuron_setN(tNeuron* const nr, float N)
{
    _tNeuron* n = *nr;
    n->gN = N;
}

void tNeuron_setC(tNeuron* const nr, float C)
{
    _tNeuron* n = *nr;
    n->C = C;
    n->rate[2] = n->gL/n->C;
}

float tNeuron_tick(tNeuron* const nr)
{
    _tNeuron* n = *nr;
    
    float output = 0.0f;
    float voltage = n->voltage;
    
    n->alpha[0] = (0.01f * (10.0f - voltage)) / (expf((10.0f - voltage)/10.0f) - 1.0f);
    n->alpha[1] = (0.1f * (25.0f-voltage)) / (expf((25.0f-voltage)/10.0f) - 1.0f);
    n->alpha[2] = (0.07f * expf((-1.0f * voltage)/20.0f));
    
    n->beta[0] = (0.125f * expf((-1.0f* voltage)/80.0f));
    n->beta[1] = (4.0f * expf((-1.0f * voltage)/18.0f));
    n->beta[2] = (1.0f / (expf((30.0f-voltage)/10.0f) + 1.0f));
    
    for (int i = 0; i < 3; i++)
    {
        n->P[i] = (n->alpha[i] * n->timeStep) + ((1.0f - ((n->alpha[i] + n->beta[i]) * n->timeStep)) * n->P[i]);
        
        if (n->P[i] > 1.0f)         n->P[i] = 0.0f;
        else if (n->P[i] < -1.0f)   n->P[i] = 0.0f;
    }
    // rate[0]= k ; rate[1] = Na ; rate[2] = l
    n->rate[0] = ((n->gK * powf(n->P[0], 4.0f)) / n->C);
    n->rate[1] = ((n->gN * powf(n->P[1], 3.0f) * n->P[2]) / n->C);
    
    //calculate the final membrane voltage based on the computed variables
    n->voltage = voltage +
    (n->timeStep * n->current / n->C) -
    (n->timeStep * ( n->rate[0] * (voltage - n->V[0]) + n->rate[1] * (voltage - n->V[1]) + n->rate[2] * (voltage - n->V[2])));
    
    if (n->mode == NeuronTanh)
    {
        n->voltage = 100.0f * tanhf(0.01f * n->voltage);
    }
    else if (n->mode == NeuronAaltoShaper)
    {
        float shapeVoltage = 0.01f * n->voltage;
        
        float w, c, xc, xc2, xc4;
        
        float sqrt8 = 2.82842712475f;
        
        float wscale = 1.30612244898f;
        float m_drive = 1.0f;
        
        xc = LEAF_clip(-sqrt8, shapeVoltage, sqrt8);
        
        xc2 = xc*xc;
        
        c = 0.5f * shapeVoltage * (3.0f - (xc2));
        
        xc4 = xc2 * xc2;
        
        w = (1.0f - xc2 * 0.25f + xc4 * 0.015625f) * wscale;
        
        shapeVoltage = w * (c + 0.05f * xc2) * (m_drive + 0.75f);
        
        n->voltage = 100.0f * shapeVoltage;
    }
    
    
    if (n->voltage > 100.0f)  n->voltage = 100.0f;
    else if (n->voltage < -100.) n->voltage = -100.0f;
    
    //(inputCurrent + (voltage - ((voltage * timeStep) / timeConstant)) + P[0] + P[1] + P[2]) => voltage;
    // now we should have a result
    //set the output voltage to the "step" ugen, which controls the DAC.
    output = n->voltage * 0.01f; // volts
    
    output = tPoleZero_tick(&n->f, output);
    
    return output;
    
}

void tNeuron_setMode  (tNeuron* const nr, NeuronMode mode)
{
    _tNeuron* n = *nr;
    n->mode = mode;
}

void tNeuron_setCurrent  (tNeuron* const nr, float current)
{
    _tNeuron* n = *nr;
    n->current = current;
}



//----------------------------------------------------------------------------------------------------------

void tMBPulse_init(tMBPulse* const osc, LEAF* const leaf)
{
    tMBPulse_initToPool(osc, &leaf->mempool);
}
                          
void tMBPulse_initToPool(tMBPulse* const osc, tMempool* const pool)
{
    _tMempool* m = *pool;
    _tMBPulse* c = *osc = (_tMBPulse*) mpool_alloc(sizeof(_tMBPulse), m);
    c->mempool = m;
    
    c->_init = true;
    c->amp = 1.0f;
    c->freq = 440.f;
    c->lastsyncin = 0.0f;
    c->sync = 0.0f;
    c->syncdir = 1.0f;
    c->softsync = 0;
    c->waveform = 0.0f;
    c->_z = 0.0f;
    c->_j = 0;
    memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
}

void tMBPulse_free(tMBPulse* const osc)
{
    _tMBPulse* c = *osc;
    mpool_free((char*)c, c->mempool);
}

float tMBPulse_tick(tMBPulse* const osc)
{
    _tMBPulse* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    int    j, k;
    float  freq, sync;
    float  a, b, p, w, x, z, sw;
    
    sync = c->sync;
    freq = c->freq;
    p = c->_p;  /* phase [0, 1) */
    w = c->_w;  /* phase increment */
    b = c->_b;  /* duty cycle (0, 1) */
    x = c->_x;  /* temporary output variable */
    z = c->_z;  /* low pass filter state */
    j = c->_j;  /* index into buffer _f */
    k = c->_k;  /* output state, 0 = high (0.5f), 1 = low (-0.5f) */
    //
    if (c->_init) {
        p = 0.0f;
        
        w = freq * leaf->invSampleRate;
        b = 0.5f * (1.0f + c->waveform );
        
        /* for variable-width rectangular wave, we could do DC compensation with:
         *     x = 1.0f - b;
         * but that doesn't work well with highly modulated hard sync.  Instead,
         * we keep things in the range [-0.5f, 0.5f]. */
        x = 0.5f;
        /* if we valued alias-free startup over low startup time, we could do:
         *   p -= w;
         *   place_step_dd(_f, j, 0.0f, w, 0.5f); */
        k = 0;
        c->_init = false;
    }
    //
    //    a = 0.2 + 0.8 * vco->_port [FILT];
    a = 0.5f; // when a = 1, LPfilter is disabled
    
    w = freq * leaf->invSampleRate;
    b = 0.5f * (1.0f + c->waveform);

    if (sync > 0.0f && c->softsync > 0) c->syncdir = -c->syncdir;
    
    sw = w * c->syncdir;
    p += sw - (int)sw;
    
    if (sync > 0.0f && c->softsync == 0) {  /* sync to master */
        float eof_offset = sync * sw;
        float p_at_reset = p - eof_offset;
    
        if (sw > 0) p = eof_offset;
        else if (sw < 0) p = 1.0f - eof_offset;
        
        /* place any DDs that may have occurred in subsample before reset */
        if (!k) {
            if (sw > 0)
            {
                if (p_at_reset >= b) {
                    place_step_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -1.0f);
                    k = 1;
                    x = -0.5f;
                }
                if (p_at_reset >= 1.0f) {
                    p_at_reset -= 1.0f;
                    place_step_dd(c->_f, j, p_at_reset + eof_offset, sw, 1.0f);
                    k = 0;
                    x = 0.5f;
                }
            }
            else if (sw < 0)
            {
                if (p_at_reset < 0.0f) {
                    p_at_reset += 1.0f;
                    place_step_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, -1.0f);
                    k = 1;
                    x = -0.5f;
                }
                if (k && p_at_reset < b) {
                    place_step_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, 1.0f);
                    k = 0;
                    x = 0.5f;
                }
            }
        } else {
            if (sw > 0)
            {
                if (p_at_reset >= 1.0f) {
                    p_at_reset -= 1.0f;
                    place_step_dd(c->_f, j, p_at_reset + eof_offset, sw, 1.0f);
                    k = 0;
                    x = 0.5f;
                }
                if (!k && p_at_reset >= b) {
                    place_step_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -1.0f);
                    k = 1;
                    x = -0.5f;
                }
            }
            else if (sw < 0)
            {
                if (p_at_reset < b) {
                    place_step_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, 1.0f);
                    k = 0;
                    x = 0.5f;
                }
                if (p_at_reset < 0.0f) {
                    p_at_reset += 1.0f;
                    place_step_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, -1.0f);
                    k = 1;
                    x = -0.5f;
                }
            }
        }
        
        /* now place reset DD */
        if (sw > 0)
        {
            if (k) {
                place_step_dd(c->_f, j, p, sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
            if (p >= b) {
                place_step_dd(c->_f, j, p - b, sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
        }
        else if (sw < 0)
        {
            if (!k) {
                place_step_dd(c->_f, j, 1.0f - p, -sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
            if (p < b) {
                place_step_dd(c->_f, j, b - p, -sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
        }
    } else if (!k) {  /* normal operation, signal currently high */
        
        if (sw > 0)
        {
            if (p >= b) {
                place_step_dd(c->_f, j, p - b, sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
            if (p >= 1.0f) {
                p -= 1.0f;
                place_step_dd(c->_f, j, p, sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
        }
        else if (sw < 0)
        {
            if (p < 0.0f) {
                p += 1.0f;
                place_step_dd(c->_f, j, 1.0f - p, -sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
            if (k && p < b) {
                place_step_dd(c->_f, j, b - p, -sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
        }
        
    } else {  /* normal operation, signal currently low */
        
        if (sw > 0)
        {
            if (p >= 1.0f) {
                p -= 1.0f;
                place_step_dd(c->_f, j, p, sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
            if (!k && p >= b) {
                place_step_dd(c->_f, j, p - b, sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
        }
        else if (sw < 0)
        {
            if (p < b) {
                place_step_dd(c->_f, j, b - p, -sw, 1.0f);
                k = 0;
                x = 0.5f;
            }
            if (p < 0.0f) {
                p += 1.0f;
                place_step_dd(c->_f, j, 1.0f - p, -sw, -1.0f);
                k = 1;
                x = -0.5f;
            }
        }
    }
    c->_f[j + DD_SAMPLE_DELAY] += x;
    
    z += a * (c->_f[j] - z);
    c->out = c->amp * z;
    
    if (++j == FILLEN)
    {
        j = 0;
        memcpy (c->_f, c->_f + FILLEN, STEP_DD_PULSE_LENGTH * sizeof (float));
        memset (c->_f + STEP_DD_PULSE_LENGTH, 0,  FILLEN * sizeof (float));
    }
    
    c->_p = p;
    c->_w = w;
    c->_b = b;
    c->_x = x;
    c->_z = z;
    c->_j = j;
    c->_k = k;
    
    return c->out;
}

void tMBPulse_setFreq(tMBPulse* const osc, float f)
{
    _tMBPulse* c = *osc;
    c->freq = f;
}

void tMBPulse_setWidth(tMBPulse* const osc, float w)
{
    _tMBPulse* c = *osc;
    c->waveform = w;
}

float tMBPulse_sync(tMBPulse* const osc, float value)
{
    _tMBPulse* c = *osc;
    
    //based on https://github.com/VCVRack/Fundamental/blob/5799ee2a9b21492b42ebcb9b65d5395ef5c1cbe2/src/VCO.cpp#L123
    float last = c->lastsyncin;
    float delta = value - last;
    float crossing = -last / delta;
    c->lastsyncin = value;
    if ((0.f < crossing) && (crossing <= 1.f) && (value >= 0.f))
        c->sync = (1.f - crossing) * delta;
    else c->sync = 0.f;
    
    return value;
}

void tMBPulse_setSyncMode(tMBPulse* const osc, int hardOrSoft)
{
    _tMBPulse* c = *osc;
    c->softsync = hardOrSoft > 0 ? 1 : 0;
}

//==========================================================================================================
//==========================================================================================================

void tMBTriangle_init(tMBTriangle* const osc, LEAF* const leaf)
{
    tMBTriangle_initToPool(osc, &leaf->mempool);
}

void tMBTriangle_initToPool(tMBTriangle* const osc, tMempool* const pool)
{
    _tMempool* m = *pool;
    _tMBTriangle* c = *osc = (_tMBTriangle*) mpool_alloc(sizeof(_tMBTriangle), m);
    c->mempool = m;
    
    c->amp = 1.0f;
    c->freq = 440.f;
    c->lastsyncin = 0.0f;
    c->sync = 0.0f;
    c->syncdir = 1.0f;
    c->softsync = 0;
    c->waveform = 0.0f;
    c->_init = true;
    c->_z = 0.0f;
    c->_j = 0;
    memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
}

void tMBTriangle_free(tMBTriangle* const osc)
{
    _tMBTriangle* c = *osc;
    mpool_free((char*)c, c->mempool);
}

float tMBTriangle_tick(tMBTriangle* const osc)
{
    _tMBTriangle* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    int    j, k, dir;
    float  freq, sync;
    float  a, b, b1, p, w, sw, x, z;
    
    sync = c->sync;
    dir = c->syncdir;
    freq = c->freq;
    p = c->_p;  /* phase [0, 1) */
    w = c->_w;  /* phase increment */
    b = c->_b;  /* duty cycle (0, 1) */
    z = c->_z;  /* low pass filter state */
    j = c->_j;  /* index into buffer _f */
    k = c->_k;  /* output state, 0 = positive slope, 1 = negative slope */
    
    if (c->_init) {
        //        w = (exp2ap (freq[1] + vco->_port[OCTN] + vco->_port[TUNE] + expm[1] * vco->_port[EXPG] + 8.03136)
        //                + 1e3 * linm[1] * vco->_port[LING]) / SAMPLERATE;
        w = freq * leaf->invSampleRate;
        b = 0.5f * (1.0f + c->waveform);
        p = 0.5f * b;
        /* if we valued alias-free startup over low startup time, we could do:
         *   p -= w;
         *   place_slope_dd(_f, j, 0.0f, w, 1.0f / b); */
        k = 0;
        c->_init = false;
    }
    
    //    a = 0.2 + 0.8 * vco->_port [FILT];
    a = 0.5f; // when a = 1, LPfilter is disabled
    
    w = freq * leaf->invSampleRate;
    b = 0.5f * (1.0f + c->waveform);
    b1 = 1.0f - b;
    
    if (sync > 0.0f && c->softsync > 0) c->syncdir = -c->syncdir;
    
    sw = w * c->syncdir;
    p += sw - (int)sw;
    
    if (sync > 0.0f && c->softsync == 0) {  /* sync to master */
        float eof_offset = sync * sw;
        float p_at_reset = p - eof_offset;
        
        if (sw > 0) p = eof_offset;
        else if (sw < 0) p = 1.0f - eof_offset;
        //
        /* place any DDs that may have occurred in subsample before reset */
            
        if (!k) {
            x = -0.5f + p_at_reset / b;
            if (sw > 0)
            {
                if (p_at_reset >= b) {
                    x = 0.5f - (p_at_reset - b) / b1;
                    place_slope_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -1.0f / b1 - 1.0f / b);
                    k = 1;
                }
                if (p_at_reset >= 1.0f) {
                    p_at_reset -= 1.0f;
                    x = -0.5f + p_at_reset / b;
                    place_slope_dd(c->_f, j, p_at_reset + eof_offset, sw, 1.0f / b + 1.0f / b1);
                    k = 0;
                }
            }
            else if (sw < 0)
            {
                if (p_at_reset < 0.0f) {
                    p_at_reset += 1.0f;
                    x = 0.5f - (p_at_reset - b) / b1;
                    place_slope_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, 1.0f / b + 1.0f / b1);
                    k = 1;
                }
                if (k && p_at_reset < b) {
                    x = -0.5f + p_at_reset / b;
                    place_slope_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, -1.0f / b1 - 1.0f / b);
                    k = 0;
                }
            }
        } else {
            x = 0.5f - (p_at_reset - b) / b1;
            if (sw > 0)
            {
                if (p_at_reset >= 1.0f) {
                    p_at_reset -= 1.0f;
                    x = -0.5f + p_at_reset / b;
                    place_slope_dd(c->_f, j, p_at_reset + eof_offset, sw, 1.0f / b + 1.0f / b1);
                    k = 0;
                }
                if (!k && p_at_reset >= b) {
                    x = 0.5f - (p_at_reset - b) / b1;
                    place_slope_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -1.0f / b1 - 1.0f / b);
                    k = 1;
                }
            }
            else if (sw < 0)
            {
                if (p_at_reset < b) {
                    x = -0.5f + p_at_reset / b;
                    place_slope_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, -1.0f / b1 - 1.0f / b);
                    k = 0;
                }
                if (p_at_reset < 0.0f) {
                    p_at_reset += 1.0f;
                    x = 0.5f - (p_at_reset - b) / b1;
                    place_slope_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, 1.0f / b + 1.0f / b1);
                    k = 1;
                }
            }
        }
        
        /* now place reset DDs */
        if (sw > 0)
        {
            if (k)
                place_slope_dd(c->_f, j, p, sw, 1.0f / b + 1.0f / b1);
            place_step_dd(c->_f, j, p, sw, -0.5f - x);
            x = -0.5f + p / b;
            k = 0;
            if (p >= b) {
                x = 0.5f - (p - b) / b1;
                place_slope_dd(c->_f, j, p - b, sw, -1.0f / b1 - 1.0f / b);
                k = 1;
            }
        }
        else if (sw < 0)
        {
            if (!k)
                place_slope_dd(c->_f, j, 1.0f - p, -sw, 1.0f / b + 1.0f / b1);
            place_step_dd(c->_f, j, 1.0f - p, -sw, -0.5f - x);
            x = 0.5f - (p - b) / b1;
            k = 1;
            if (p < b) {
                x = -0.5f + p / b;
                place_slope_dd(c->_f, j, b - p, -sw, -1.0f / b1 - 1.0f / b);
                k = 0;
            }
        }
    } else if (!k) {  /* normal operation, slope currently up */
        
        x = -0.5f + p / b;
        if (sw > 0)
        {
            if (p >= b) {
                x = 0.5f - (p - b) / b1;
                place_slope_dd(c->_f, j, p - b, sw, -1.0f / b1 - 1.0f / b);
                k = 1;
            }
            if (p >= 1.0f) {
                p -= 1.0f;
                x = -0.5f + p / b;
                place_slope_dd(c->_f, j, p, sw, 1.0f / b + 1.0f / b1);
                k = 0;
            }
        }
        else if (sw < 0)
        {
            if (p < 0.0f) {
                p += 1.0f;
                x = 0.5f - (p - b) / b1;
                place_slope_dd(c->_f, j, 1.0f - p, -sw, 1.0f / b + 1.0f / b1);
                k = 1;
            }
            if (k && p < b) {
                x = -0.5f + p / b;
                place_slope_dd(c->_f, j, b - p, -sw, -1.0f / b1 - 1.0f / b);
                k = 0;
            }
        }
        
    } else {  /* normal operation, slope currently down */
        
        x = 0.5f - (p - b) / b1;
        if (sw > 0)
        {
            if (p >= 1.0f) {
                p -= 1.0f;
                x = -0.5f + p / b;
                place_slope_dd(c->_f, j, p, sw, 1.0f / b + 1.0f / b1);
                k = 0;
            }
            if (!k && p >= b) {
                x = 0.5f - (p - b) / b1;
                place_slope_dd(c->_f, j, p - b, sw, -1.0f / b1 - 1.0f / b);
                k = 1;
            }
        }
        else if (sw < 0)
        {
            if (p < b) {
                x = -0.5f + p / b;
                place_slope_dd(c->_f, j, b - p, -sw, -1.0f / b1 - 1.0f / b);
                k = 0;
            }
            if (p < 0.0f) {
                p += 1.0f;
                x = 0.5f - (p - b) / b1;
                place_slope_dd(c->_f, j, 1.0f - p, -sw, 1.0f / b + 1.0f / b1);
                k = 1;
            }
        }
    }
    c->_f[j + DD_SAMPLE_DELAY] += x;
    
    z += a * (c->_f[j] - z);
    c->out = c->amp * z;
    
    if (++j == FILLEN)
    {
        j = 0;
        memcpy (c->_f, c->_f + FILLEN, STEP_DD_PULSE_LENGTH * sizeof (float));
        memset (c->_f + STEP_DD_PULSE_LENGTH, 0,  FILLEN * sizeof (float));
    }
    
    c->_p = p;
    c->_w = w;
    c->_b = b;
    c->_z = z;
    c->_j = j;
    c->_k = k;
    
    return c->out;
}

void tMBTriangle_setFreq(tMBTriangle* const osc, float f)
{
    _tMBTriangle* c = *osc;
    c->freq = f;
}

void tMBTriangle_setWidth(tMBTriangle* const osc, float w)
{
    _tMBTriangle* c = *osc;
    c->waveform = w;
}

float tMBTriangle_sync(tMBTriangle* const osc, float value)
{
    _tMBTriangle* c = *osc;
    
    //based on https://github.com/VCVRack/Fundamental/blob/5799ee2a9b21492b42ebcb9b65d5395ef5c1cbe2/src/VCO.cpp#L123
    float last = c->lastsyncin;
    float delta = value - last;
    float crossing = -last / delta;
    c->lastsyncin = value;
    if ((0.f < crossing) && (crossing <= 1.f) && (value >= 0.f))
        c->sync = (1.f - crossing) * delta;
    else c->sync = 0.f;
    
    return value;
}

void tMBTriangle_setSyncMode(tMBTriangle* const osc, int hardOrSoft)
{
    _tMBTriangle* c = *osc;
    c->softsync = hardOrSoft > 0 ? 1 : 0;
}

//==========================================================================================================
//==========================================================================================================

void tMBSaw_init(tMBSaw* const osc, LEAF* const leaf)
{
    tMBSaw_initToPool(osc, &leaf->mempool);
}

void tMBSaw_initToPool(tMBSaw* const osc, tMempool* const pool)
{
    _tMempool* m = *pool;
    _tMBSaw* c = *osc = (_tMBSaw*) mpool_alloc(sizeof(_tMBSaw), m);
    c->mempool = m;
    
    c->_init = true;
    c->amp = 1.0f;
    c->freq = 440.f;
    c->lastsyncin = 0.0f;
    c->sync = 0.0f;
    c->syncdir = 1.0f;
    c->softsync = 0;
    c->_z = 0.0f;
    c->_j = 0;
    memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
}

void tMBSaw_free(tMBSaw* const osc)
{
    _tMBSaw* c = *osc;
    mpool_free((char*)c, c->mempool);
}

float tMBSaw_tick(tMBSaw* const osc)
{
    _tMBSaw* c = *osc;
    LEAF* leaf = c->mempool->leaf;
    
    int    j;
    float  freq, sync;
    float  a, p, w, sw, z;
    
    sync = c->sync;
    freq = c->freq;
    
    p = c->_p;  /* phase [0, 1) */
    w = c->_w;  /* phase increment */
    z = c->_z;  /* low pass filter state */
    j = c->_j;  /* index into buffer _f */
    
    if (c->_init) {
        p = 0.5f;
        w = freq * leaf->invSampleRate;
        
        /* if we valued alias-free startup over low startup time, we could do:
         *   p -= w;
         *   place_slope_dd(_f, j, 0.0f, w, -1.0f); */
        c->_init = false;
    }
    
    //a = 0.2 + 0.8 * vco->_port [FILT];
    a = 0.5f; // when a = 1, LPfilter is disabled
    
    w = freq * leaf->invSampleRate;

    if (sync > 0.0f && c->softsync > 0) c->syncdir = -c->syncdir;
    // Should insert minblep for softsync?
    //            if (p_at_reset >= 1.0f) {
    //                p_at_reset -= (int)p_at_reset;
    //                place_slope_dd(c->_f, j, p_at_reset + eof_offset, sw, 2.0f);
    //            }
    //            if (p_at_reset < 0.0f) {
    //                p_at_reset += 1.0f - (int)p_at_reset;
    //                place_slope_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, -2.0f);
    //            }
    //            if (sw > 0) place_slope_dd(c->_f, j, p, sw, 2.0f);
    //            else if (sw < 0) place_slope_dd(c->_f, j, 1.0f - p, -sw, -2.0f);
    
    sw = w * c->syncdir;
    p += sw - (int)sw;
    
    if (sync > 0.0f && c->softsync == 0) {  /* sync to master */
        float eof_offset = sync * sw;
        float p_at_reset = p - eof_offset;

        if (sw > 0) p = eof_offset;
        else if (sw < 0) p = 1.0f - eof_offset;
        
        /* place any DD that may have occurred in subsample before reset */
        if (p_at_reset >= 1.0f) {
            p_at_reset -= 1.0f;
            place_step_dd(c->_f, j, p_at_reset + eof_offset, sw, 1.0f);
        }
        if (p_at_reset < 0.0f) {
            p_at_reset += 1.0f;
            place_step_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, -1.0f);
        }
        
        /* now place reset DD */
        if (sw > 0)
            place_step_dd(c->_f, j, p, sw, p_at_reset);
        else if (sw < 0)
            place_step_dd(c->_f, j, 1.0f - p, -sw, -p_at_reset);

    } else if (p >= 1.0f) {  /* normal phase reset */
        p -= 1.0f;
        place_step_dd(c->_f, j, p, sw, 1.0f);
        
    } else if (p < 0.0f) {
        p += 1.0f;
        place_step_dd(c->_f, j, 1.0f - p, -sw, -1.0f);
    }
    c->_f[j + DD_SAMPLE_DELAY] += 0.5f - p;
    
    z += a * (c->_f[j] - z); // LP filtering
    c->out = c->amp * z;
    
    if (++j == FILLEN)
    {
        j = 0;
        memcpy (c->_f, c->_f + FILLEN, STEP_DD_PULSE_LENGTH * sizeof (float));
        memset (c->_f + STEP_DD_PULSE_LENGTH, 0,  FILLEN * sizeof (float));
    }
    
    c->_p = p;
    c->_w = w;
    c->_z = z;
    c->_j = j;
    
    return c->out;
}

void tMBSaw_setFreq(tMBSaw* const osc, float f)
{
    _tMBSaw* c = *osc;
    c->freq = f;
}

float tMBSaw_sync(tMBSaw* const osc, float value)
{
    _tMBSaw* c = *osc;
    
    //based on https://github.com/VCVRack/Fundamental/blob/5799ee2a9b21492b42ebcb9b65d5395ef5c1cbe2/src/VCO.cpp#L123
    float last = c->lastsyncin;
    float delta = value - last;
    float crossing = -last / delta;
    c->lastsyncin = value;
    if ((0.f < crossing) && (crossing <= 1.f) && (value >= 0.f))
        c->sync = (1.f - crossing) * delta;
    else c->sync = 0.f;
    
    return value;
}

void tMBSaw_setSyncMode(tMBSaw* const osc, int hardOrSoft)
{
    _tMBSaw* c = *osc;
    c->softsync = hardOrSoft > 0 ? 1 : 0;
}