shithub: leaf

Download patch

ref: ffa74901871dab0d6d96ba82414d800748cb01c1
parent: 08efd6ad79ec698103bcfc7567e036b4d1d9ef10
author: spiricom <jeff@snyderphonics.com>
date: Thu Apr 23 09:22:01 EDT 2020

added vz filters

binary files a/.DS_Store b/.DS_Store differ
binary files a/LEAF/.DS_Store b/LEAF/.DS_Store differ
--- a/LEAF/Inc/leaf-envelopes.h
+++ b/LEAF/Inc/leaf-envelopes.h
@@ -161,8 +161,36 @@
     void    tRamp_setDest       (tRamp* const, float dest);
     void    tRamp_setVal        (tRamp* const, float val);
 
+    ///=============
+    /* Ramp Updown*/
+    typedef struct _tRampUpDown {
+        float upInc;
+        float downInc;
+        float inv_sr_ms;
+        float minimum_time;
+        float curr,dest;
+        float upTime;
+        float downTime;
+        int samples_per_tick;
+
+    } _tRampUpDown;
+
+    typedef _tRampUpDown* tRampUpDown;
+
+    void    tRampUpDown_init          (tRampUpDown* const, float upTime, float downTime, int samplesPerTick);
+    void    tRampUpDown_free          (tRampUpDown* const);
+    void    tRampUpDown_initToPool    (tRampUpDown* const, float upTime, float downTime, int samplesPerTick, tMempool* const);
+    void    tRampUpDown_freeFromPool  (tRampUpDown* const, tMempool* const);
     
+    float   tRampUpDown_tick          (tRampUpDown* const);
+    float   tRampUpDown_sample        (tRampUpDown* const);
+    void    tRampUpDown_setUpTime       (tRampUpDown* const, float upTime);
+    void    tRampUpDown_setDownTime       (tRampUpDown* const, float downTime);
+    void    tRampUpDown_setDest       (tRampUpDown* const, float dest);
+    void    tRampUpDown_setVal        (tRampUpDown* const, float val);
 
+
+    ///=========================
     //Slide (based on Max/MSP's slide~)
 	typedef struct _tSlide {
 		float prevOut;
--- a/LEAF/Inc/leaf-filters.h
+++ b/LEAF/Inc/leaf-filters.h
@@ -240,6 +240,8 @@
         SVFTypeBandpass,
         SVFTypeNotch,
         SVFTypePeak,
+		SVFTypeLowShelf,
+		SVFTypeHighShelf
     } SVFType;
     
     typedef struct _tSVF
@@ -247,7 +249,7 @@
         SVFType type;
         float cutoff, Q;
         float ic1eq,ic2eq;
-        float g,k,a1,a2,a3;
+        float g,k,a1,a2,a3,cH,cB,cL, kAmount;
         
     } _tSVF;
     
@@ -376,6 +378,65 @@
 	void    tMedianFilter_freeFromPool   (tMedianFilter* const, tMempool* const);
 
 	float   tMedianFilter_tick           (tMedianFilter* const, float input);
+
+
+
+
+
+	//Vadim Zavalishin style from VA book (from implementation in RSlib posted to kvr forum)
+
+    typedef enum VZFilterType
+    {
+    	Highpass = 0,
+        Lowpass,
+        BandpassSkirt,
+		BandpassPeak,
+		BandReject,
+        Bell,
+		Lowshelf,
+		Highshelf,
+		Morph,
+		Bypass,
+		Allpass
+    } VZFilterType;
+
+
+	typedef struct _tVZFilter
+	    {
+			VZFilterType type;
+			// state:
+			float s1, s2;
+
+			// filter coefficients:
+			float g;          // embedded integrator gain
+			float R2;         // twice the damping coefficient (R2 == 2*R == 1/Q)
+			float h;          // factor for feedback (== 1/(1+2*R*g+g*g))
+			float cL, cB, cH; // coefficients for low-, band-, and highpass signals
+
+			// parameters:
+			float fs;    // sample-rate
+			float fc;    // characteristic frequency
+			float G;     // gain
+			float B;     // bandwidth (in octaves)
+			float m;     // morph parameter (0...1)
+
+	    } _tVZFilter;
+
+	    typedef _tVZFilter* tVZFilter;
+
+	    void    tVZFilter_init           (tVZFilter* const, VZFilterType type, float freq, float Q);
+		void    tVZFilter_free           (tVZFilter* const);
+		void    tVZFilter_initToPool     (tVZFilter* const, VZFilterType type, float freq, float Q, tMempool* const);
+		void    tVZFilter_freeFromPool   (tVZFilter* const, tMempool* const);
+
+		float   tVZFilter_tick           	(tVZFilter* const, float input);
+		void   tVZFilter_calcCoeffs           (tVZFilter* const);
+		void   tVZFilter_setBandwidth        	(tVZFilter* const, float bandWidth);
+		void   tVZFilter_setFreq           (tVZFilter* const, float freq);
+		void   tVZFilter_setGain          		(tVZFilter* const, float gain);
+		void   tVZFilter_setType          		(tVZFilter* const, VZFilterType type);
+		float 	tVZFilter_BandwidthToR		(tVZFilter* const vf, float B);
+
 
 #ifdef __cplusplus
 }
--- a/LEAF/Inc/leaf-math.h
+++ b/LEAF/Inc/leaf-math.h
@@ -33,7 +33,7 @@
         float f;
         int i;
     };
-    
+
 #define SQRT8 2.82842712475f
 #define WSCALE 1.30612244898f
 #define PI              (3.14159265358979f)
@@ -90,6 +90,7 @@
 #define TWO_TO_32        4294967296.0f
 #define INV_TWO_TO_32    0.000000000232831f
     
+#define ONE_OVER_SQRT2	0.707106781186548f
 
 #define LOGTEN 2.302585092994
 
@@ -170,6 +171,14 @@
     float atodb(float a);
 
     float dbtoa(float db);
+
+    float fastdbtoa(float db);
+
+    float maximum (float num1, float num2);
+
+    float minimum (float num1, float num2);
+
+
     //==============================================================================
     
 #ifdef __cplusplus
--- a/LEAF/Src/leaf-analysis.c
+++ b/LEAF/Src/leaf-analysis.c
@@ -609,7 +609,7 @@
     int n, tindex = s->timeindex;
     int framesize = s->framesize;
     int mask = framesize - 1;
-    float norm = 1.0f / sqrtf((float)(framesize * 2));
+    float norm = 1. / sqrt((float)(framesize * 2));
     
     float *inputbuf = s->inputbuf;
     float *processbuf = s->processbuf;
--- a/LEAF/Src/leaf-effects.c
+++ b/LEAF/Src/leaf-effects.c
@@ -1209,7 +1209,7 @@
 {
     _tRetune* r = *rt;
     
-    return (r->inputPeriod * leaf.invSampleRate);
+    return r->inputPeriod;
 }
 
 float tRetune_getInputFreq(tRetune* const rt)
@@ -1216,7 +1216,7 @@
 {
     _tRetune* r = *rt;
     
-    return 1.0f/(r->inputPeriod * leaf.invSampleRate);
+    return 1.0f/r->inputPeriod;
 }
 
 //============================================================================================================
--- a/LEAF/Src/leaf-envelopes.c
+++ b/LEAF/Src/leaf-envelopes.c
@@ -700,6 +700,151 @@
 }
 
 
+
+/* RampUpDown */
+void    tRampUpDown_init(tRampUpDown* const r, float upTime, float downTime, int samples_per_tick)
+{
+	tRampUpDown_initToPool(r, upTime, downTime, samples_per_tick, &leaf.mempool);
+}
+
+void tRampUpDown_free(tRampUpDown* const r)
+{
+	tRampUpDown_freeFromPool(r, &leaf.mempool);
+}
+
+void    tRampUpDown_initToPool(tRampUpDown* const r, float upTime, float downTime, int samples_per_tick, tMempool* const mp)
+{
+    _tMempool* m = *mp;
+    _tRampUpDown* ramp = *r = (_tRampUpDown*) mpool_alloc(sizeof(_tRampUpDown), m);
+
+    ramp->inv_sr_ms = 1.0f/(leaf.sampleRate*0.001f);
+    ramp->minimum_time = ramp->inv_sr_ms * samples_per_tick;
+    ramp->curr = 0.0f;
+    ramp->dest = 0.0f;
+
+    if (upTime < ramp->minimum_time)
+    {
+        ramp->upTime = ramp->minimum_time;
+    }
+    else
+    {
+        ramp->upTime = upTime;
+    }
+
+    if (downTime < ramp->minimum_time)
+    {
+        ramp->downTime = ramp->minimum_time;
+    }
+    else
+    {
+        ramp->downTime = downTime;
+    }
+
+    ramp->samples_per_tick = samples_per_tick;
+    ramp->upInc = ((ramp->dest - ramp->curr) / ramp->upTime * ramp->inv_sr_ms) * (float)ramp->samples_per_tick;
+    ramp->downInc = ((ramp->dest - ramp->curr) / ramp->downTime * ramp->inv_sr_ms) * (float)ramp->samples_per_tick;
+}
+
+void    tRampUpDown_freeFromPool  (tRampUpDown* const r, tMempool* const mp)
+{
+    _tMempool* m = *mp;
+    _tRampUpDown* ramp = *r;
+
+    mpool_free(ramp, m);
+}
+
+void     tRampUpDown_setUpTime(tRampUpDown* const ramp, float upTime)
+{
+    _tRampUpDown* r = *ramp;
+
+    if (upTime < r->minimum_time)
+    {
+        r->upTime = r->minimum_time;
+    }
+    else
+    {
+        r->upTime = upTime;
+    }
+    r->upInc = ((r->dest - r->curr) / r->upTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+}
+
+
+void     tRampUpDown_setDownTime(tRampUpDown* const ramp, float downTime)
+{
+    _tRampUpDown* r = *ramp;
+
+    if (downTime < r->minimum_time)
+    {
+        r->downTime = r->minimum_time;
+    }
+    else
+    {
+        r->downTime = downTime;
+    }
+    r->downInc = ((r->dest - r->curr) / r->downTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+}
+
+void     tRampUpDown_setDest(tRampUpDown* const ramp, float dest)
+{
+    _tRampUpDown* r = *ramp;
+    r->dest = dest;
+    r->upInc = ((r->dest - r->curr) / r->upTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+    r->downInc = ((r->dest - r->curr) / r->downTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+}
+
+void     tRampUpDown_setVal(tRampUpDown* const ramp, float val)
+{
+    _tRampUpDown* r = *ramp;
+    r->curr = val;
+    r->upInc = ((r->dest - r->curr) / r->upTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+    r->downInc = ((r->dest - r->curr) / r->downTime * r->inv_sr_ms) * (float)r->samples_per_tick;
+}
+
+float   tRampUpDown_tick(tRampUpDown* const ramp)
+{
+    _tRampUpDown* r = *ramp;
+    float test;
+
+    if (r->dest < r->curr)
+    {
+    	test = r->curr + r->downInc;
+    	if (test > r->dest)
+    	{
+    		r->curr = test;
+    	}
+    	else
+    	{
+    		r->downInc = 0.0f;
+    		r->curr = r->dest;
+    	}
+    }
+    else if (r->dest > r->curr)
+    {
+    	test = r->curr + r->upInc;
+    	if (test < r->dest)
+    	{
+    		r->curr = test;
+    	}
+    	else
+    	{
+    		r->upInc = 0.0f;
+    		r->curr = r->dest;
+    	}
+    }
+    return r->curr;
+}
+
+float   tRampUpDown_sample(tRampUpDown* const ramp)
+{
+    _tRampUpDown* r = *ramp;
+    return r->curr;
+}
+
+
+
+
+
+
 /* Exponential Smoother */
 void    tExpSmooth_init(tExpSmooth* const expsmooth, float val, float factor)
 {	// factor is usually a value between 0 and 0.1. Lower value is slower. 0.01 for example gives you a smoothing time of about 10ms
--- a/LEAF/Src/leaf-filters.c
+++ b/LEAF/Src/leaf-filters.c
@@ -855,6 +855,51 @@
     svf->a1 = 1.0f/(1.0f + svf->g * (svf->g + svf->k));
     svf->a2 = svf->g*svf->a1;
     svf->a3 = svf->g*svf->a2;
+
+    if (type == SVFTypeLowpass)
+    {
+		svf->cH = 0.0f;
+		svf->cB = 0.0f;
+		svf->kAmount = 0.0f;
+		svf->cL = 1.0f;
+    }
+    else if (type == SVFTypeBandpass)
+    {
+		svf->cH = 0.0f;
+		svf->cB = 1.0f;
+		svf->kAmount = 0.0f;
+		svf->cL = 0.0f;
+    }
+
+    else if (type == SVFTypeHighpass)
+    {
+		svf->cH = 1.0f;
+		svf->cB = svf->k * -1.0f;
+		svf->kAmount = 1.0f;
+		svf->cL = -1.0f;
+    }
+
+    else if (type == SVFTypeNotch)
+    {
+		svf->cH = 1.0f;
+		svf->cB = svf->k * -1.0f;
+		svf->kAmount = 1.0f;
+		svf->cL = 0.0f;
+    }
+
+
+    else if (type == SVFTypePeak)
+    {
+		svf->cH = 1.0f;
+		svf->cB = svf->k * -1.0f;
+		svf->kAmount = 1.0f;
+		svf->cL = -2.0f;
+    }
+    // or maybe this?
+    /*
+     * hp=1 bp=A/Q (where A is 10^(G/40) and G is gain in decibels) and lp = 1
+     */
+
 }
 
 void tSVF_free(tSVF* const svff)
@@ -879,6 +924,9 @@
     svf->a1 = 1.0f/(1.0f + svf->g * (svf->g + svf->k));
     svf->a2 = svf->g*svf->a1;
     svf->a3 = svf->g*svf->a2;
+    svf->cH = 0.0f;
+    svf->cB = 0.0f;
+    svf->cL = 1.0f;
 }
 
 void    tSVF_freeFromPool   (tSVF* const svff, tMempool* const mp)
@@ -900,13 +948,7 @@
     svf->ic1eq = (2.0f * v1) - svf->ic1eq;
     svf->ic2eq = (2.0f * v2) - svf->ic2eq;
     
-    if (svf->type == SVFTypeLowpass)        return v2;
-    else if (svf->type == SVFTypeBandpass)  return v1;
-    else if (svf->type == SVFTypeHighpass)  return v0 - (svf->k * v1) - v2;
-    else if (svf->type == SVFTypeNotch)     return v0 - (svf->k * v1);
-    else if (svf->type == SVFTypePeak)      return v0 - (svf->k * v1) - (2.0f * v2);
-    else                                    return 0.0f;
-    
+    return (v0 * svf->cH) + (svf->kAmount * svf->k * v1 * svf->cB) + (v2 * svf->cL);
 }
 
 void     tSVF_setFreq(tSVF* const svff, float freq)
@@ -924,11 +966,22 @@
     _tSVF* svf = *svff;
     
     svf->k = 1.0f/Q;
+
     svf->a1 = 1.0f/(1.0f + svf->g * (svf->g + svf->k));
     svf->a2 = svf->g * svf->a1;
     svf->a3 = svf->g * svf->a2;
 }
 
+void 	tSVF_setFreqAndQ(tSVF* const svff, float freq, float Q)
+{
+    _tSVF* svf = *svff;
+    svf->k = 1.0f/Q;
+    svf->g = tanf(PI * freq * leaf.invSampleRate);
+    svf->a1 = 1.0f/(1.0f + svf->g * (svf->g + svf->k));
+    svf->a2 = svf->g * svf->a1;
+    svf->a3 = svf->g * svf->a2;
+}
+
 // Efficient version of tSVF where frequency is set based on 12-bit integer input for lookup in tanh wavetable.
 void   tEfficientSVF_init(tEfficientSVF* const svff, SVFType type, uint16_t input, float Q)
 {
@@ -1324,3 +1377,215 @@
 }
 
 
+void    tVZFilter_init           (tVZFilter* const vf, VZFilterType type, float freq, float bandWidth)
+{
+	tVZFilter_initToPool(vf, type, freq, bandWidth, &leaf.mempool);
+}
+
+void    tVZFilter_free           (tVZFilter* const vf)
+{
+	tVZFilter_freeFromPool(vf, &leaf.mempool);
+}
+void    tVZFilter_initToPool     (tVZFilter* const vf, VZFilterType type, float freq, float bandWidth, tMempool* const mp)
+{
+
+	 _tMempool* m = *mp;
+	 _tVZFilter* f = *vf = (_tVZFilter*) mpool_alloc(sizeof(_tVZFilter), m);
+	f->fc   = freq;
+	f->type = type;
+	f->G    = ONE_OVER_SQRT2;
+	f->B    = bandWidth;
+	f->m    = 0.0f;
+	f->s1 = 0.0f;
+	f->s2 = 0.0f;
+	tVZFilter_calcCoeffs(vf);
+
+
+}
+void    tVZFilter_freeFromPool   (tVZFilter* const vf, tMempool* const mp)
+{
+	 _tMempool* m = *mp;
+		 _tVZFilter* f = *vf = (_tVZFilter*) mpool_alloc(sizeof(_tVZFilter), m);
+		 mpool_free(f, m);
+}
+
+float   tVZFilter_tick           	(tVZFilter* const vf, float in)
+{
+	_tVZFilter* f = *vf;
+
+	float yL, yB, yH;
+
+	// compute highpass output via Eq. 5.1:
+	yH = (in - f->R2*f->s1 - f->g*f->s1 - f->s2) * f->h;
+
+	// compute bandpass output by applying 1st integrator to highpass output:
+	yB = tanhf(f->g*yH) + f->s1;
+	f->s1 = f->g*yH + yB; // state update in 1st integrator
+
+	// compute lowpass output by applying 2nd integrator to bandpass output:
+	yL = tanhf(f->g*yB) + f->s2;
+	f->s2 = f->g*yB + yL; // state update in 2nd integrator
+
+	//according to the Vadim paper, we could add saturation to this model by adding a tanh in the integration stage.
+	//
+	//seems like that might look like this:
+    // y = tanh(g*x) + s; // output computation
+    // s = g*x + y; // state update
+
+	//instead of this:
+    // y = g*x + s; // output computation
+    // s = g*x + y; // state update
+
+	return f->cL*yL + f->cB*yB + f->cH*yH;
+
+}
+
+void   tVZFilter_calcCoeffs           (tVZFilter* const vf)
+{
+
+	_tVZFilter* f = *vf;
+	f->g = tanf(PI * f->fc * leaf.invSampleRate);  // embedded integrator gain (Fig 3.11)
+
+	  switch( f->type )
+	  {
+	  case Bypass:
+		{
+		  f->R2 = 1.0f / f->G;  // can we use an arbitrary value here, for example R2 = 1?
+		  f->cL = 1.0f;
+		  f->cB = f->R2;
+		  f->cH = 1.0f;
+		}
+		break;
+	  case Lowpass:
+		{
+			f->R2 = 1.0f / f->G;
+			f->cL = 1.0f; f->cB = 0.0f; f->cH = 0.0f;
+		}
+		break;
+	  case Highpass:
+		{
+			f->R2 = 1.0f / f->G;
+			f->cL = 0.0f; f->cB = 0.0f; f->cH = 1.0f;
+		}
+		break;
+	  case BandpassSkirt:
+		{
+			f->R2 = 1.0f / f->G;
+			f->cL = 0.0f; f->cB = 1.0f; f->cH = 0.0f;
+		}
+		break;
+	  case BandpassPeak:
+		{
+			f->R2 = 2.0f*tVZFilter_BandwidthToR(vf, f->B);
+			f->cL = 0.0f; f->cB = f->R2; f->cH = 0.0f;
+		}
+		break;
+	  case BandReject:
+		{
+			f->R2 = 2.0f*tVZFilter_BandwidthToR(vf, f->B);
+			f->cL = 1.0f; f->cB = 0.0f; f->cH = 1.0f;
+		}
+		break;
+	  case Bell:
+		{
+			float fl = f->fc*powf(2.0f, (-f->B)*0.5f); // lower bandedge frequency (in Hz)
+			float wl = tanf(PI*fl*leaf.invSampleRate);   // warped radian lower bandedge frequency /(2*fs)
+			float r  = f->g/wl;
+			r *= r;    // warped frequency ratio wu/wl == (wc/wl)^2 where wu is the
+									   // warped upper bandedge, wc the center
+			f->R2 = 2.0f*sqrtf(((r*r+1.0f)/r-2.0f)/(4.0f*f->G));
+			f->cL = 1.0f; f->cB = f->R2*f->G; f->cH = 1.0f;
+		}
+		break;
+	  case Lowshelf:
+		{
+			float A = sqrtf(f->G);
+		  f->g /= sqrtf(A);               // scale SVF-cutoff frequency for shelvers
+		  f->R2 = 2*sinhf(f->B*logf(2.0f)*0.5f);
+		  f->cL = f->G; f->cB = f->R2*A; f->cH = 1.0f;
+		}
+		break;
+	  case Highshelf:
+		{
+		  float A = sqrtf(f->G);
+		  f->g *= sqrtf(A);               // scale SVF-cutoff frequency for shelvers
+		  f->R2 = 2.0f*sinhf(f->B*logf(2.0f)*0.5f);
+		  f->cL = 1.0f; f->cB = f->R2*A; f->cH = f->G;
+		}
+		break;
+	  case Allpass:
+		{
+			f->R2 = 2.0f*tVZFilter_BandwidthToR(vf, f->B);
+			f->cL = 1.0f; f->cB = -f->R2; f->cH = 1.0f;
+		}
+		break;
+
+		// experimental - maybe we must find better curves for cL, cB, cH:
+	  case Morph:
+		{
+			f->R2 = 1.0f / f->G;
+		  float x  = 2.0f*f->m-1.0f;
+
+		  f->cL = maximum(-x, 0.0f); /*cL *= cL;*/
+		  f->cH = minimum( x, 0.0f); /*cH *= cH;*/
+		  f->cB = 1.0f-x*x;
+
+			// bottom line: we need to test different versions for how they feel when tweaking the
+			// morph parameter
+
+		  // this scaling ensures constant magnitude at the cutoff point (we divide the coefficients by
+		  // the magnitude response value at the cutoff frequency and scale back by the gain):
+		  float s = f->G * sqrtf((f->R2*f->R2) / (f->cL*f->cL + f->cB*f->cB + f->cH*f->cH - 2.0f*f->cL*f->cH));
+		  f->cL *= s; f->cB *= s; f->cH *= s;
+		}
+		break;
+
+	  }
+
+	  f->h = 1.0f / (1.0f + f->R2*f->g + f->g*f->g);  // factor for feedback precomputation
+}
+
+
+void   tVZFilter_setBandwidth           	(tVZFilter* const vf, float B)
+{
+	_tVZFilter* f = *vf;
+	f->B = LEAF_clip(0.0f, B, 100.0f);
+	tVZFilter_calcCoeffs(vf);
+}
+void   tVZFilter_setFreq           (tVZFilter* const vf, float freq)
+{
+	_tVZFilter* f = *vf;
+	f->fc = LEAF_clip(0.0f, freq, 0.5f*leaf.sampleRate);
+	tVZFilter_calcCoeffs(vf);
+}
+void   tVZFilter_setGain          		(tVZFilter* const vf, float gain)
+{
+	_tVZFilter* f = *vf;
+	f->G = LEAF_clip(0.000001f, gain, 100.0f);
+	tVZFilter_calcCoeffs(vf);
+}
+
+void   tVZFilter_setMorph          		(tVZFilter* const vf, float morph)
+{
+	_tVZFilter* f = *vf;
+	f->m = LEAF_clip(0.0f, morph, 1.0f);
+	tVZFilter_calcCoeffs(vf);
+}
+
+void tVZFilter_setType         		(tVZFilter* const vf, VZFilterType type)
+{
+	_tVZFilter* f = *vf;
+	f->type = type;
+	tVZFilter_calcCoeffs(vf);
+}
+
+float tVZFilter_BandwidthToR(tVZFilter* const vf, float B)
+{
+	_tVZFilter* f = *vf;
+  float fl = f->fc*powf(2.0f, -B*0.5f); // lower bandedge frequency (in Hz)
+  float gl = tanf(PI*fl*leaf.invSampleRate);   // warped radian lower bandedge frequency /(2*fs)
+  float r  = gl/f->g;            // ratio between warped lower bandedge- and center-frequencies
+							   // unwarped: r = pow(2, -B/2) -> approximation for low
+							   // center-frequencies
+  return sqrtf((1.0f-r*r)*(1.0f-r*r)/(4.0f*r*r));
+}
--- a/LEAF/Src/leaf-math.c
+++ b/LEAF/Src/leaf-math.c
@@ -514,4 +514,21 @@
 }
 
 
+float fastdbtoa(float db)
+{
+	//return powf(10.0f, db * 0.05f);
+	return expf(0.115129254649702f * db); //faster version from http://openaudio.blogspot.com/2017/02/faster-log10-and-pow.html
+}
+
+
+float maximum (float num1, float num2)
+{
+	return (num1 > num2 ) ? num1 : num2;
+}
+
+float minimum (float num1, float num2)
+{
+	return (num1 < num2 ) ? num1 : num2;
+}
+
 
--- a/LEAF/Src/leaf-oscillators.c
+++ b/LEAF/Src/leaf-oscillators.c
@@ -227,7 +227,7 @@
     float out = 0.0f;
     
     int idx = (int)(c->phase * SQR_TABLE_SIZE);
-
+    
     // Wavetable synthesis
     out = __leaf_table_squarewave[c->oct+1][idx] +
          (__leaf_table_squarewave[c->oct][idx] - __leaf_table_squarewave[c->oct+1][idx]) * c->w;