shithub: leaf

Download patch

ref: 30ed191b459ec3101ccd4fbc3fe5bf6ed97e5f8f
parent: c353a6bf6608f6c238562592ad210f62eae87ce6
author: Jeffrey Snyder <jeffsnyder@jeffreys-mbp.mynetworksettings.com>
date: Wed Aug 24 11:21:27 EDT 2022

keep up with the Joneses

--- a/leaf/Inc/leaf-math.h
+++ b/leaf/Inc/leaf-math.h
@@ -18,7 +18,6 @@
 #include "stdint.h"
 #include "stdlib.h"
 #include "limits.h"
-    
     //==============================================================================
     
     //==============================================================================
@@ -234,11 +233,7 @@
     float median3f(float a, float b, float c);
 
     void place_step_dd(float *buffer, int index, float phase, float w, float scale);
-
     void place_slope_dd(float *buffer, int index, float phase, float w, float slope_delta);
-
-    /*! @} */
-
     //==============================================================================
     
 #ifdef __cplusplus
--- a/leaf/Inc/leaf-oscillators.h
+++ b/leaf/Inc/leaf-oscillators.h
@@ -721,8 +721,6 @@
         
         tMempool mempool;
         float    out;
-        float    amp;
-        float    last_amp;
         float    freq;
         float    waveform;    // duty cycle, must be in [-1, 1]
         float    lastsyncin;
@@ -732,7 +730,6 @@
         float   _p, _w, _b, _x, _z;
         int     _j, _k;
         float   _f [FILLEN + STEP_DD_PULSE_LENGTH];
-        bool    _init;
         float invSampleRate;
     } _tMBPulse;
     
@@ -748,7 +745,7 @@
     float tMBPulse_sync(tMBPulse* const osc, float sync);
     void tMBPulse_setPhase(tMBPulse* const osc, float phase);
     void tMBPulse_setSyncMode(tMBPulse* const osc, int hardOrSoft);
-    void tMBPulse_setBufferOffset(tMBPulse* const osc, int offset);
+    void tMBPulse_setBufferOffset(tMBPulse* const osc, uint32_t offset);
     void tMBPulse_setSampleRate (tMBPulse* const osc, float sr);
     
     /*!
@@ -796,8 +793,6 @@
         
         tMempool mempool;
         float    out;
-        float    amp;
-        float    last_amp;
         float    freq;
         float    waveform;    // duty cycle, must be in [-1, 1]
         float    lastsyncin;
@@ -807,7 +802,6 @@
         float   _p, _w, _b, _z;
         int     _j, _k;
         float   _f [FILLEN + LONGEST_DD_PULSE_LENGTH];
-        bool    _init;
         float invSampleRate;
     } _tMBTriangle;
     
@@ -823,7 +817,7 @@
     float tMBTriangle_sync(tMBTriangle* const osc, float sync);
     void tMBTriangle_setPhase(tMBTriangle* const osc, float phase);
     void tMBTriangle_setSyncMode(tMBTriangle* const osc, int hardOrSoft);
-    void tMBTriangle_setBufferOffset(tMBTriangle* const osc, int offset);
+    void tMBTriangle_setBufferOffset(tMBTriangle* const osc, uint32_t offset);
     void tMBTriangle_setSampleRate (tMBTriangle* const osc, float sr);
     
     
@@ -871,8 +865,6 @@
     {
         tMempool mempool;
         float    out;
-        float    amp;
-        float    last_amp;
         float    freq;
         float    lastsyncin;
         float    sync;
@@ -881,7 +873,6 @@
         float   _p, _w, _z;
         int     _j;
         float   _f [FILLEN + STEP_DD_PULSE_LENGTH];
-        bool    _init;
         float invSampleRate;
     } _tMBSaw;
     
@@ -896,11 +887,83 @@
     float tMBSaw_sync(tMBSaw* const osc, float sync);
     void tMBSaw_setPhase(tMBSaw* const osc, float phase);
     void tMBSaw_setSyncMode(tMBSaw* const osc, int hardOrSoft);
-    void tMBSaw_setBufferOffset(tMBSaw* const osc, int offset);
+    void tMBSaw_setBufferOffset(tMBSaw* const osc, uint32_t offset);
     void tMBSaw_setSampleRate (tMBSaw* const osc, float sr);
     
     //==============================================================================
+    /*!
+     @defgroup tmbsaw tMBSawPulse
+     @ingroup oscillators
+     @brief Saw wave mixed with Pulse wave oscillator with minBLEP anti-aliasing.
+     @{
+
+     @fn void tMBSaw_init(tMBSaw* const osc, LEAF* const leaf)
+     @brief Initialize a tMBSaw to the default mempool of a LEAF instance.
+     @param osc A pointer to the tMBSaw to initialize.
+
+     @fn void tMBSaw_initToPool(tMBSaw* const osc, tMempool* const mempool)
+     @brief Initialize a tMBSaw to a specified mempool.
+     @param osc A pointer to the tMBSaw to initialize.
+
+     @fn void tMBSaw_free(tMBSaw* const osc)
+     @brief Free a tMBSaw from its mempool.
+     @param osc A pointer to the tMBSaw to free.
+
+     @fn float tMBSaw_tick(tMBSaw* const osc)
+     @brief Tick the oscillator.
+     @param osc A pointer to the relevant tMBSaw.
+     @return The ticked sample.
+
+     @fn void tMBSaw_setFreq(tMBSaw* const osc, float f)
+     @brief Set the frequency of the oscillator.
+     @param osc A pointer to the relevant tMBSaw.
+     @param freq The new frequency.
+
+     @fn float tMBSaw_sync(tMBSaw* const osc, float sync)
+     @brief Sync this oscillator to another signal.
+     @param osc A pointer to the relevant tMBSaw.
+     @param sync A sample of the signal to sync to.
+     @return The passed in sample.
+
+     @fn void tMBSaw_setSyncMode(tMBSaw* const osc, int hardOrSoft)
+     @brief Set the sync behavior of the oscillator.
+     @param hardOrSoft 0 for hard sync, 1 for soft sync
+     
+     @} */
+
+    typedef struct _tMBSawPulse
+    {
+        tMempool mempool;
+        float    out;
+        float    freq;
+        float    lastsyncin;
+        float    sync;
+        float    syncdir;
+        int      softsync;
+        float    waveform;
+        float   _p, _w, _b, _x, _z, _k;
+        int     _j;
+        float   _f [FILLEN + STEP_DD_PULSE_LENGTH];
+        float invSampleRate;
+        float 	shape;
+    } _tMBSawPulse;
     
+    typedef _tMBSawPulse* tMBSawPulse;
+
+    void tMBSawPulse_init(tMBSawPulse* const osc, LEAF* const leaf);
+    void tMBSawPulse_initToPool(tMBSawPulse* const osc, tMempool* const mempool);
+    void tMBSawPulse_free(tMBSawPulse* const osc);
+
+    float tMBSawPulse_tick(tMBSawPulse* const osc);
+    void tMBSawPulse_setFreq(tMBSawPulse* const osc, float f);
+    float tMBSawPulse_sync(tMBSawPulse* const osc, float sync);
+    void tMBSawPulse_setPhase(tMBSawPulse* const osc, float phase);
+    void tMBSawPulse_setShape(tMBSawPulse* const osc, float shape);
+    void tMBSawPulse_setSyncMode(tMBSawPulse* const osc, int hardOrSoft);
+    void tMBSawPulse_setBufferOffset(tMBSawPulse* const osc, uint32_t offset);
+    void tMBSawPulse_setSampleRate (tMBSawPulse* const osc, float sr);
+
+    //==============================================================================
     /*!
      @defgroup ttable tTable
      @ingroup oscillators
--- a/leaf/Src/leaf-filters.c
+++ b/leaf/Src/leaf-filters.c
@@ -17,9 +17,12 @@
 #include "../Inc/leaf-filters.h"
 #include "../Inc/leaf-tables.h"
 #include "../leaf.h"
-//#include "tim.h"
 #endif
 
+#ifdef ARM_MATH_CM7
+#include "arm_math.h"
+#endif
+
 // ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ OnePole Filter ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ //
 void    tAllpass_init(tAllpass* const ft, float initDelay, uint32_t maxDelay, LEAF* const leaf)
 {
@@ -1821,12 +1824,15 @@
     }
     return ((a + 105.0f)*a + 945.0f) / output;
 }
+
 volatile int errorCheckCheck = 0;
+//#define SAFE_FILTER
 float   tDiodeFilter_tick               (tDiodeFilter* const vf, float in)
 {
     _tDiodeFilter* f = *vf;
-    
+#ifdef SAFE_FILTER
     int errorCheck = 0;
+#endif
     // the input x[n+1] is given by 'in', and x[n] by zi
     // input with half delay
     float ih = 0.5f * (in + f->zi);
@@ -1840,24 +1846,30 @@
     
     // This formula gives the result for y3 thanks to MATLAB
     float y3 = (f->s2 + f->s3 + t2*(f->s1 + f->s2 + f->s3 + t1*(f->s0 + f->s1 + f->s2 + f->s3 + t0*in)) + t1*(2.0f*f->s2 + 2.0f*f->s3))*t3 + f->s3 + 2.0f*f->s3*t1 + t2*(2.0f*f->s3 + 3.0f*f->s3*t1);
+#ifdef SAFE_FILTER
     if (isnan(y3))
     {
         errorCheck = 1;
     }
+#endif
     float tempy3denom = (t4 + t1*(2.0f*t4 + 4.0f) + t2*(t4 + t1*(t4 + f->r*t0 + 4.0f) + 3.0f) + 2.0f)*t3 + t4 + t1*(2.0f*t4 + 2.0f) + t2*(2.0f*t4 + t1*(3.0f*t4 + 3.0f) + 2.0f) + 1.0f;
+#ifdef SAFE_FILTER
     if (isnan(tempy3denom))
     {
         errorCheck = 2;
     }
+#endif
     if (tempy3denom == 0.0f)
     {
         tempy3denom = 0.000001f;
     }
     y3 = y3 / tempy3denom;
+#ifdef SAFE_FILTER
     if (isnan(y3))
     {
         errorCheck = 3;
     }
+#endif
     if (t1 == 0.0f)
     {
         t1 = 0.000001f;
@@ -1878,19 +1890,22 @@
     
     // update state
     f->s0 += 2.0f * (t0*xx + t1*(y1-y0));
+#ifdef SAFE_FILTER
     if (isnan(f->s0))
     {
         errorCheck = 4;
     }
-    
+
     if (isinf(f->s0))
     {
         errorCheck = 5;
     }
+
     if (errorCheck != 0)
     {
-        errorCheckCheck = 1;
+        errorCheckCheck = errorCheck;
     }
+#endif
     f->s1 += 2.0f * (t2*(y2-y1) - t1*(y1-y0));
     f->s2 += 2.0f * (t3*(y3-y2) - t2*(y2-y1));
     f->s3 += 2.0f * (-t4*(y3) - t3*(y3-y2));
@@ -1975,7 +1990,16 @@
 
 float smoothABS ( float x, const float y) // y controls 'smoothness' usually between 0.002 -> 0.04
 {
-    return (sqrtf((x * x)  + y)) - sqrtf(y);
+	//possible speedup with sqrt CMSIS-DSP approximation? seems to resolve to just a normal call to sqrt. Maybe the vector version since there are two square roots to determine? -JS
+//	#ifdef ARM_MATH_CM7
+//		float output1;
+//		float output2;
+//		arm_sqrt_f32((x * x)  + y, &output1);
+//		arm_sqrt_f32(y, &output2);
+//		return output1 - output2;
+//	#else
+		return (sqrtf((x * x)  + y)) - sqrtf(y);
+//	#endif
 }
 
 float smoothclip (float x, const float a, const float b) // assuming symmetrical clipping
--- a/leaf/Src/leaf-math.c
+++ b/leaf/Src/leaf-math.c
@@ -847,39 +847,39 @@
 /// MINBLEPS
 // https://github.com/MrBlueXav/Dekrispator_v2 blepvco.c
 void place_step_dd(float *buffer, int index, float phase, float w, float scale)
-{
-    float r;
-    long i;
-    
-    r = MINBLEP_PHASES * phase / w;
-    i = lrintf(r - 0.5f);
-    r -= (float)i;
-    i &= MINBLEP_PHASE_MASK;  /* extreme modulation can cause i to be out-of-range */
-    
-    while (i < MINBLEP_PHASES * STEP_DD_PULSE_LENGTH) {
-        buffer[index] += scale * (step_dd_table[i].value + r * step_dd_table[i].delta);
-        i += MINBLEP_PHASES;
-        index++;
-    }
-}
-//----------------------------------------------------------------------------------------------------------
+    {
+        float r;
+        long i;
 
-void place_slope_dd(float *buffer, int index, float phase, float w, float slope_delta)
-{
-    float r;
-    long i;
-    
-    r = MINBLEP_PHASES * phase / w;
-    i = lrintf(r - 0.5f);
-    r -= (float)i;
-    i &= MINBLEP_PHASE_MASK;  /* extreme modulation can cause i to be out-of-range */
-    
-    slope_delta *= w;
-    
-    while (i < MINBLEP_PHASES * SLOPE_DD_PULSE_LENGTH) {
-        buffer[index] += slope_delta * (slope_dd_table[i] + r * (slope_dd_table[i + 1] - slope_dd_table[i]));
-        i += MINBLEP_PHASES;
-        index++;
+        r = MINBLEP_PHASES * phase / w;
+        i = lrintf(r - 0.5f);
+        r -= (float)i;
+        i &= MINBLEP_PHASE_MASK;  /* extreme modulation can cause i to be out-of-range */
+
+        while (i < MINBLEP_PHASES * STEP_DD_PULSE_LENGTH) {
+            buffer[index] += scale * (step_dd_table[i].value + r * step_dd_table[i].delta);
+            i += MINBLEP_PHASES;
+            index++;
+        }
     }
-}
+void place_slope_dd(float *buffer, int index, float phase, float w, float slope_delta)
+	{
+		float r;
+		long i;
+
+		r = MINBLEP_PHASES * phase / w;
+		i = lrintf(r - 0.5f);
+		r -= (float)i;
+		i &= MINBLEP_PHASE_MASK;  /* extreme modulation can cause i to be out-of-range */
+
+		slope_delta *= w;
+
+		while (i < MINBLEP_PHASES * SLOPE_DD_PULSE_LENGTH) {
+			buffer[index] += slope_delta * (slope_dd_table[i] + r * (slope_dd_table[i + 1] - slope_dd_table[i]));
+			i += MINBLEP_PHASES;
+			index++;
+		}
+	}
 #endif // LEAF_INCLUDE_MINBLEP_TABLES
+    /*! @} */
+
--- a/leaf/Src/leaf-oscillators.c
+++ b/leaf/Src/leaf-oscillators.c
@@ -938,8 +938,7 @@
     
     c->invSampleRate = leaf->invSampleRate;
     
-    c->_init = true;
-    c->amp = 1.0f;
+
     c->freq = 440.f;
     c->lastsyncin = 0.0f;
     c->sync = 0.0f;
@@ -948,6 +947,12 @@
     c->waveform = 0.0f;
     c->_z = 0.0f;
     c->_j = 0;
+    c->_p = 0.0f;  /* phase [0, 1) */
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
+    c->_b = 0.5f * (1.0f + c->waveform);  /* duty cycle (0, 1) */
+    c->_x = 0.5f;  /* temporary output variable */
+    c->_k = 0.0f;  /* output state, 0 = high (0.5f), 1 = low (-0.5f) */
+
     memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
 }
 
@@ -962,11 +967,11 @@
     _tMBPulse* c = *osc;
     
     int    j, k;
-    float  freq, sync;
-    float  a, b, p, w, x, z, sw;
+    float  sync;
+    float  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) */
@@ -974,31 +979,8 @@
     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 * c->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 * c->invSampleRate;
-    b = 0.5f * (1.0f + c->waveform);
 
+
     if (sync > 0.0f && c->softsync > 0) c->syncdir = -c->syncdir;
     
     sw = w * c->syncdir;
@@ -1163,8 +1145,8 @@
     }
     c->_f[j + DD_SAMPLE_DELAY] += x;
     
-    z += a * (c->_f[j] - z);
-    c->out = c->amp * z;
+    z += 0.5f * (c->_f[j] - z);
+    c->out = z;
     
     if (++j == FILLEN)
     {
@@ -1188,6 +1170,7 @@
 {
     _tMBPulse* c = *osc;
     c->freq = f;
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
 }
 
 void tMBPulse_setWidth(tMBPulse* const osc, float w)
@@ -1219,10 +1202,10 @@
 }
 
 //useful if you have several oscillators so the buffer refill is not synchronized
-void tMBPulse_setBufferOffset(tMBPulse* const osc, int offset)
+void tMBPulse_setBufferOffset(tMBPulse* const osc, uint32_t offset)
 {
 	_tMBPulse* c = *osc;
-	offset = LEAF_clip(0,offset, FILLEN-1);
+	offset = offset & (FILLEN-1);
 	c->_j = offset;
 }
 
@@ -1254,7 +1237,6 @@
     LEAF* leaf = c->mempool->leaf;
     
     c->invSampleRate = leaf->invSampleRate;
-    c->amp = 1.0f;
     c->freq = 440.f;
     c->lastsyncin = 0.0f;
     c->sync = 0.0f;
@@ -1261,9 +1243,12 @@
     c->syncdir = 1.0f;
     c->softsync = 0;
     c->waveform = 0.0f;
-    c->_init = true;
     c->_z = 0.0f;
     c->_j = 0;
+    c->_p = 0.0f;  /* phase [0, 1) */
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
+    c->_b = 0.5f * (1.0f + c->waveform);  /* duty cycle (0, 1) */
+    c->_k = 0.0f;  /* output state, 0 = high (0.5f), 1 = low (-0.5f) */
     memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
 }
 
@@ -1277,41 +1262,27 @@
 {
     _tMBTriangle* c = *osc;
     
-    int    j, k, dir;
-    float  freq, sync;
-    float  a, b, b1, p, w, sw, x, z;
+    int    j, k;
+    float  sync;
+    float  b, b1, invB, invB1, p, w, sw, z;
+    float  x = 0.5f;
     
     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) */
+    invB = 1.0f / b;
     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 * c->invSampleRate;
-        b = 0.5f * (1.0f + c->waveform);
-//        p = 0.5f * b;
-        p = 0.f;
-        /* 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 * c->invSampleRate;
+
     b = 0.5f * (1.0f + c->waveform);
     b1 = 1.0f - b;
-    
+    invB1 = 1.0f / b1;
     if (sync > 0.0f && c->softsync > 0) c->syncdir = -c->syncdir;
     
     sw = w * c->syncdir;
@@ -1327,18 +1298,18 @@
         /* place any DDs that may have occurred in subsample before reset */
             
         if (!k) {
-            x = -0.5f + p_at_reset / b;
+            x = -0.5f + p_at_reset * invB;
             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);
+                    x = 0.5f - (p_at_reset - b) * invB1;
+                    place_slope_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -invB1 - invB);
                     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);
+                    x = -0.5f + p_at_reset * invB;
+                    place_slope_dd(c->_f, j, p_at_reset + eof_offset, sw, invB + invB1);
                     k = 0;
                 }
             }
@@ -1346,29 +1317,29 @@
             {
                 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);
+                    x = 0.5f - (p_at_reset - b)  * invB1;
+                    place_slope_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, invB + invB1);
                     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);
+                    x = -0.5f + p_at_reset * invB;
+                    place_slope_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, -invB1 - invB);
                     k = 0;
                 }
             }
         } else {
-            x = 0.5f - (p_at_reset - b) / b1;
+            x = 0.5f - (p_at_reset - b) * invB1;
             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);
+                    x = -0.5f + p_at_reset * invB;
+                    place_slope_dd(c->_f, j, p_at_reset + eof_offset, sw, invB + invB1);
                     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);
+                    x = 0.5f - (p_at_reset - b) * invB1;
+                    place_slope_dd(c->_f, j, p_at_reset - b + eof_offset, sw, -invB1 - invB);
                     k = 1;
                 }
             }
@@ -1375,14 +1346,14 @@
             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);
+                    x = -0.5f + p_at_reset * invB;
+                    place_slope_dd(c->_f, j, b - p_at_reset - eof_offset, -sw, -invB1 - invB);
                     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);
+                    x = 0.5f - (p_at_reset - b) * invB1;
+                    place_slope_dd(c->_f, j, 1.0f - p_at_reset - eof_offset, -sw, invB + invB1);
                     k = 1;
                 }
             }
@@ -1392,13 +1363,13 @@
         if (sw > 0)
         {
             if (k)
-                place_slope_dd(c->_f, j, p, sw, 1.0f / b + 1.0f / b1);
+                place_slope_dd(c->_f, j, p, sw, invB + invB1);
             place_step_dd(c->_f, j, p, sw, -0.5f - x);
-            x = -0.5f + p / b;
+            x = -0.5f + p * invB;
             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);
+                x = 0.5f - (p - b) * invB1;
+                place_slope_dd(c->_f, j, p - b, sw, -invB1 - invB);
                 k = 1;
             }
         }
@@ -1405,30 +1376,30 @@
         else if (sw < 0)
         {
             if (!k)
-                place_slope_dd(c->_f, j, 1.0f - p, -sw, 1.0f / b + 1.0f / b1);
+                place_slope_dd(c->_f, j, 1.0f - p, -sw, invB + invB1);
             place_step_dd(c->_f, j, 1.0f - p, -sw, -0.5f - x);
-            x = 0.5f - (p - b) / b1;
+            x = 0.5f - (p - b) * invB1;
             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);
+                x = -0.5f + p * invB;
+                place_slope_dd(c->_f, j, b - p, -sw, -invB1 - invB);
                 k = 0;
             }
         }
     } else if (!k) {  /* normal operation, slope currently up */
         
-        x = -0.5f + p / b;
+        x = -0.5f + p * invB;
         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);
+                x = 0.5f - (p - b) * invB1;;
+                place_slope_dd(c->_f, j, p - b, sw, -invB1 - invB);
                 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);
+                x = -0.5f + p * invB;
+                place_slope_dd(c->_f, j, p, sw, invB + invB1);
                 k = 0;
             }
         }
@@ -1436,13 +1407,13 @@
         {
             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);
+                x = 0.5f - (p - b) * invB1;
+                place_slope_dd(c->_f, j, 1.0f - p, -sw, invB + invB1);
                 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);
+                x = -0.5f + p * invB;
+                place_slope_dd(c->_f, j, b - p, -sw, -invB1 - invB);
                 k = 0;
             }
         }
@@ -1449,18 +1420,18 @@
         
     } else {  /* normal operation, slope currently down */
         
-        x = 0.5f - (p - b) / b1;
+        x = 0.5f - (p - b) * invB1;
         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);
+                x = -0.5f + p * invB;
+                place_slope_dd(c->_f, j, p, sw, invB + invB1);
                 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);
+                x = 0.5f - (p - b) * invB1;
+                place_slope_dd(c->_f, j, p - b, sw, -invB1 - invB);
                 k = 1;
             }
         }
@@ -1467,14 +1438,14 @@
         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);
+                x = -0.5f + p * invB;
+                place_slope_dd(c->_f, j, b - p, -sw, -invB1 - invB);
                 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);
+                x = 0.5f - (p - b) * invB1;
+                place_slope_dd(c->_f, j, 1.0f - p, -sw, invB + invB1);
                 k = 1;
             }
         }
@@ -1481,8 +1452,8 @@
     }
     c->_f[j + DD_SAMPLE_DELAY] += x;
     
-    z += a * (c->_f[j] - z);
-    c->out = c->amp * z;
+    z += 0.5f * (c->_f[j] - z);
+    c->out = z;
     
     if (++j == FILLEN)
     {
@@ -1505,6 +1476,7 @@
 {
     _tMBTriangle* c = *osc;
     c->freq = f;
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
 }
 
 void tMBTriangle_setWidth(tMBTriangle* const osc, float w)
@@ -1542,10 +1514,10 @@
 }
 
 //useful if you have several oscillators so the buffer refill is not synchronized
-void tMBTriangle_setBufferOffset(tMBTriangle* const osc, int offset)
+void tMBTriangle_setBufferOffset(tMBTriangle* const osc, uint32_t offset)
 {
 	_tMBTriangle* c = *osc;
-	offset = LEAF_clip(0,offset, FILLEN-1);
+	offset = offset & (FILLEN-1);
 	c->_j = offset;
 }
 
@@ -1571,8 +1543,6 @@
     LEAF* leaf = c->mempool->leaf;
     
     c->invSampleRate = leaf->invSampleRate;
-    c->_init = true;
-    c->amp = 1.0f;
     c->freq = 440.f;
     c->lastsyncin = 0.0f;
     c->sync = 0.0f;
@@ -1580,6 +1550,9 @@
     c->softsync = 0;
     c->_z = 0.0f;
     c->_j = 0;
+    c->_p = 0.0f;  /* phase [0, 1) */
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
+
     memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
 }
 
@@ -1594,45 +1567,30 @@
     _tMBSaw* c = *osc;
     
     int    j;
-    float  freq, sync;
-    float  a, p, w, sw, z;
+    float  sync;
+    float  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;
-        p = 0.f;
-        w = freq * c->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 * c->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);
+	//	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;
@@ -1670,8 +1628,8 @@
     }
     c->_f[j + DD_SAMPLE_DELAY] += 0.5f - p;
     
-    z += a * (c->_f[j] - z); // LP filtering
-    c->out = c->amp * z;
+    z += 0.5f * (c->_f[j] - z); // LP filtering
+    c->out = z;
     
     if (++j == FILLEN)
     {
@@ -1692,6 +1650,8 @@
 {
     _tMBSaw* c = *osc;
     c->freq = f;
+
+    c->_w = c->freq * c->invSampleRate;
 }
 
 float tMBSaw_sync(tMBSaw* const osc, float value)
@@ -1723,10 +1683,10 @@
 }
 
 //useful if you have several oscillators so the buffer refill is not synchronized
-void tMBSaw_setBufferOffset(tMBSaw* const osc, int offset)
+void tMBSaw_setBufferOffset(tMBSaw* const osc, uint32_t offset)
 {
 	_tMBSaw* c = *osc;
-	offset = LEAF_clip(0,offset, FILLEN-1);
+	offset = offset & (FILLEN-1);
 	c->_j = offset;
 }
 
@@ -1733,6 +1693,336 @@
 void tMBSaw_setSampleRate(tMBSaw* const osc, float sr)
 {
     _tMBSaw* c = *osc;
+    c->invSampleRate = 1.0f/sr;
+}
+
+
+//==================================================================================================
+
+void tMBSawPulse_init(tMBSawPulse* const osc, LEAF* const leaf)
+{
+    tMBSawPulse_initToPool(osc, &leaf->mempool);
+}
+
+void tMBSawPulse_initToPool(tMBSawPulse* const osc, tMempool* const pool)
+{
+    _tMempool* m = *pool;
+    _tMBSawPulse* c = *osc = (_tMBSawPulse*) mpool_alloc(sizeof(_tMBSawPulse), m);
+    c->mempool = m;
+    LEAF* leaf = c->mempool->leaf;
+
+    c->invSampleRate = leaf->invSampleRate;
+    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;
+    c->_p = 0.0f;  /* phase [0, 1) */
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
+    c->_b = 0.5f * (1.0f + c->waveform);  /* duty cycle (0, 1) */
+    c->_x = 0.5f;  /* temporary output variable */
+    c->_k = 0.0f;  /* output state, 0 = high (0.5f), 1 = low (-0.5f) */
+
+    memset (c->_f, 0, (FILLEN + STEP_DD_PULSE_LENGTH) * sizeof (float));
+}
+
+void tMBSawPulse_free(tMBSawPulse* const osc)
+{
+    _tMBSawPulse* c = *osc;
+    mpool_free((char*)c, c->mempool);
+}
+
+
+
+
+
+
+
+float tMBSawPulse_tick(tMBSawPulse* const osc)
+{
+    _tMBSawPulse* c = *osc;
+
+    int    j, k;
+    float  sync;
+    float  b, p, w, x, z, sw;
+    float shape = c->shape;
+    float sawShape = 1.0f - c->shape;
+    sync = c->sync;
+    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 (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;
+
+        //pulse stuff
+		 /* 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 * shape);
+					 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 * shape);
+					 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 * shape);
+					 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 * shape);
+					 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 for pulse */
+		if (sw > 0)
+		{
+			if (k) {
+				place_step_dd(c->_f, j, p, sw, 1.0f * shape);
+				k = 0;
+				x = 0.5f;
+			}
+			if (p >= b) {
+				place_step_dd(c->_f, j, p - b, sw, -1.0f * shape);
+				k = 1;
+				x = -0.5f;
+			}
+		}
+		else if (sw < 0)
+		{
+			if (!k) {
+				place_step_dd(c->_f, j, 1.0f - p, -sw, -1.0f * shape);
+				k = 1;
+				x = -0.5f;
+			}
+			if (p < b) {
+				place_step_dd(c->_f, j, b - p, -sw, 1.0f * shape);
+				k = 0;
+				x = 0.5f;
+			}
+		}
+        /* now place reset DD for saw*/
+        if (sw > 0)
+            place_step_dd(c->_f, j, p, sw, p_at_reset * sawShape);
+        else if (sw < 0)
+            place_step_dd(c->_f, j, 1.0f - p, -sw, -p_at_reset * sawShape);
+
+    }
+
+
+    else if (!k)
+    {  /* normal operation for pulse, signal currently high */
+
+		if (sw > 0)
+		{
+			if (p >= b) {
+				place_step_dd(c->_f, j, p - b, sw, -1.0f * shape);
+				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 * shape);
+				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 * shape);
+				k = 1;
+				x = -0.5f;
+			}
+		}
+		else if (sw < 0)
+		{
+			if (p < b) {
+				place_step_dd(c->_f, j, b - p, -sw, 1.0f * shape);
+				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] += ((0.5f - p) * sawShape); //saw
+
+    c->_f[j + DD_SAMPLE_DELAY] += (x * shape);//pulse
+
+    z += 0.5f * (c->_f[j] - z); // LP filtering
+    c->out = 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 tMBSawPulse_setFreq(tMBSawPulse* const osc, float f)
+{
+    _tMBSawPulse* c = *osc;
+    c->freq = f;
+    c->_w = c->freq * c->invSampleRate;  /* phase increment */
+}
+
+float tMBSawPulse_sync(tMBSawPulse* const osc, float value)
+{
+    _tMBSawPulse* 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 tMBSawPulse_setPhase(tMBSawPulse* const osc, float phase)
+{
+    _tMBSawPulse* c = *osc;
+    c->_p = phase;
+}
+
+void tMBSawPulse_setShape(tMBSawPulse* const osc, float shape)
+{
+    _tMBSawPulse* c = *osc;
+    c->shape = shape;
+}
+
+void tMBSawPulse_setSyncMode(tMBSawPulse* const osc, int hardOrSoft)
+{
+    _tMBSawPulse* c = *osc;
+    c->softsync = hardOrSoft > 0 ? 1 : 0;
+}
+
+//useful if you have several oscillators so the buffer refill is not synchronized
+void tMBSawPulse_setBufferOffset(tMBSawPulse* const osc, uint32_t offset)
+{
+	_tMBSawPulse* c = *osc;
+	offset = offset & (FILLEN-1);
+	c->_j = offset;
+}
+
+void tMBSawPulse_setSampleRate(tMBSawPulse* const osc, float sr)
+{
+    _tMBSawPulse* c = *osc;
     c->invSampleRate = 1.0f/sr;
 }