shithub: leaf

Download patch

ref: 0400bc13cf30359da0fedf61cc0f24436ed1babe
parent: 32f24276cd8e22709bacbbbefabb659553c6f1e6
author: Matthew Wang <mjw7@princeton.edu>
date: Wed Jan 6 05:29:23 EST 2021

improved wavetable oscs; added tCompactWavetable

--- a/TestPlugin/LEAF.jucer
+++ b/TestPlugin/LEAF.jucer
@@ -109,8 +109,8 @@
   <EXPORTFORMATS>
     <XCODE_MAC targetFolder="Builds/MacOSX" microphonePermissionNeeded="1">
       <CONFIGURATIONS>
-        <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="OOPS" enablePluginBinaryCopyStep="1"/>
-        <CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="OOPS"
+        <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="LEAF" enablePluginBinaryCopyStep="1"/>
+        <CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="LEAF"
                        enablePluginBinaryCopyStep="1"/>
       </CONFIGURATIONS>
       <MODULEPATHS>
@@ -136,8 +136,8 @@
                   iosBackgroundAudio="1" iPadScreenOrientation="UIInterfaceOrientationPortrait,UIInterfaceOrientationLandscapeLeft,UIInterfaceOrientationLandscapeRight"
                   iosDeviceFamily="1,2">
       <CONFIGURATIONS>
-        <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="OOPS" enablePluginBinaryCopyStep="1"/>
-        <CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="OOPS"
+        <CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="LEAF" enablePluginBinaryCopyStep="1"/>
+        <CONFIGURATION name="Release" isDebug="0" optimisation="3" targetName="LEAF"
                        enablePluginBinaryCopyStep="1"/>
       </CONFIGURATIONS>
       <MODULEPATHS>
@@ -161,10 +161,10 @@
     <VS2017 targetFolder="Builds/VisualStudio2017">
       <CONFIGURATIONS>
         <CONFIGURATION name="Debug" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
-                       isDebug="1" optimisation="1" targetName="OOPS" debugInformationFormat="ProgramDatabase"
+                       isDebug="1" optimisation="1" targetName="LEAF" debugInformationFormat="ProgramDatabase"
                        enablePluginBinaryCopyStep="0"/>
         <CONFIGURATION name="Release" winWarningLevel="4" generateManifest="1" winArchitecture="x64"
-                       isDebug="0" optimisation="3" targetName="OOPS" debugInformationFormat="ProgramDatabase"
+                       isDebug="0" optimisation="3" targetName="LEAF" debugInformationFormat="ProgramDatabase"
                        enablePluginBinaryCopyStep="0" linkTimeOptimisation="1"/>
       </CONFIGURATIONS>
       <MODULEPATHS>
--- a/TestPlugin/Source/MyTest.cpp
+++ b/TestPlugin/Source/MyTest.cpp
@@ -9,7 +9,6 @@
 #include "LEAFTest.h"
 #include "MyTest.h"
 
-
 static void leaf_pool_report(void);
 static void leaf_pool_dump(void);
 static void run_pool_test(void);
@@ -18,11 +17,6 @@
 tMBTriangle btri;
 tMBPulse bpulse;
 
-tRetune retune;
-tSimpleRetune sretune;
-
-tCompressor compressor;
-
 tSVF lp, hp;
 
 tPeriodDetection pd;
@@ -32,6 +26,12 @@
 
 tTriangle tri;
 
+tNoise noise;
+tButterworth bw;
+
+tWavetable wt;
+tCompactWavetable cwt;
+
 tBuffer samp;
 tMBSampler sampler;
 
@@ -55,9 +55,8 @@
 {
     LEAF_init(&leaf, sampleRate, blockSize, memory, MSIZE, &getRandomFloat);
     
-    tRetune_init(&retune, 1, mtof(48), mtof(72), 2048, &leaf);
-    tSimpleRetune_init(&sretune, 1, mtof(48), mtof(72), 2048, &leaf);
-    tSimpleRetune_setMode(&sretune, 1);
+    tWavetable_init(&wt, __leaf_table_sawtooth[0], 2048, 10000.f, &leaf);
+    tCompactWavetable_init(&cwt, __leaf_table_sawtooth[0], 2048, 10000.f, &leaf);
 }
 
 inline double getSawFall(double angle) {
@@ -72,7 +71,9 @@
 float   LEAFTest_tick            (float input)
 {
 //    return tRetune_tick(&retune, input)[0];
-    return tSimpleRetune_tick(&sretune, input);
+//    return tSimpleRetune_tick(&sretune, input);
+    return tWavetable_tick(&wt);
+    return tCompactWavetable_tick(&cwt);
 }
 
 int firstFrame = 1;
@@ -80,8 +81,10 @@
 void    LEAFTest_block           (void)
 {
     float val = getSliderValue("slider1");
-    tRetune_tuneVoice(&retune, 0, val * 3.0f + 0.5f);
-    tSimpleRetune_tuneVoice(&sretune, 0, 300);
+    tWavetable_setFreq(&wt, val * 40000.);
+    tCompactWavetable_setFreq(&cwt, val * 10000.);
+//    tRetune_tuneVoice(&retune, 0, val * 3.0f + 0.5f);
+//    tSimpleRetune_tuneVoice(&sretune, 0, 300);
 
     val = getSliderValue("slider2");
 //    tRetune_setPitchFactor(&retune, val * 3.0f + 0.5f, 1);
--- a/leaf/Inc/leaf-global.h
+++ b/leaf/Inc/leaf-global.h
@@ -17,6 +17,12 @@
     
 #include "leaf-mempool.h"
     
+#if _WIN32 || _WIN64
+#include "..\leaf-config.h"
+#else
+#include "../leaf-config.h"
+#endif
+    
     /*!
      * @ingroup leaf
      * @brief Struct for an instance of LEAF.
--- a/leaf/Inc/leaf-math.h
+++ b/leaf/Inc/leaf-math.h
@@ -35,6 +35,7 @@
     };
 
 #define SQRT8 2.82842712475f
+#define LEAF_SQRT2 1.41421356237f
 #define WSCALE 1.30612244898f
 #define PI              (3.14159265358979f)
 #define TWO_PI          (6.28318530717958f)
--- a/leaf/Inc/leaf-oscillators.h
+++ b/leaf/Inc/leaf-oscillators.h
@@ -17,8 +17,8 @@
 #include "leaf-mempool.h"
 #include "leaf-tables.h"
 #include "leaf-filters.h"
+#include "leaf-distortion.h"
     
-    
     /*!
      Header.
      @include basic-oscillators.h
@@ -91,7 +91,7 @@
      @brief Anti-aliased wavetable oscillator.
      @{
      
-     @fn void    tWavetable_init  (tTable* const osc, float* table, int size, LEAF* const leaf)
+     @fn void    tWavetable_init  (tWavetable* const osc, float* table, int size, LEAF* const leaf)
      @brief Initialize a tWavetable to the default mempool of a LEAF instance.
      @param osc A pointer to the tWavetable to initialize.
      @param table A pointer to the wavetable data.
@@ -98,7 +98,7 @@
      @param size The number of samples in the wavetable.
      @param leaf A pointer to the leaf instance.
      
-     @fn void    tWavetable_initToPool   (tTable* const osc, float* table, int size, tMempool* const mempool)
+     @fn void    tWavetable_initToPool   (tWavetable* const osc, float* table, int size, tMempool* const mempool)
      @brief Initialize a tWavetable to a specified mempool.
      @param osc A pointer to the tWavetable to initialize.
      @param table A pointer to the wavetable data.
@@ -105,16 +105,16 @@
      @param size The number of samples in the wave table.
      @param mempool A pointer to the tMempool to use.
      
-     @fn void    tWavetable_free         (tTable* const osc)
+     @fn void    tWavetable_free         (tWavetable* const osc)
      @brief Free a tWavetable from its mempool.
      @param osc A pointer to the tWavetable to free.
      
-     @fn float   tWavetable_tick         (tTable* const osc)
+     @fn float   tWavetable_tick         (tWavetable* const osc)
      @brief Tick a tWavetable oscillator.
      @param osc A pointer to the relevant tWavetable.
      @return The ticked sample as a float from -1 to 1.
      
-     @fn void    tWavetable_setFreq      (tTable* const osc, float freq)
+     @fn void    tWavetable_setFreq      (tWavetable* const osc, float freq)
      @brief Set the frequency of a tWavetable oscillator.
      @param osc A pointer to the relevant tWavetable.
      @param freq The frequency to set the oscillator to.
@@ -134,6 +134,7 @@
         
         int oct;
         float w;
+        float aa;
         
         tButterworth bl;
     } _tWavetable;
@@ -140,12 +141,82 @@
     
     typedef _tWavetable* tWavetable;
     
-    void    tWavetable_init(tWavetable* const osc, float* table, int size, LEAF* const leaf);
-    void    tWavetable_initToPool(tWavetable* const osc, float* table, int size, tMempool* const mempool);
+    void    tWavetable_init(tWavetable* const osc, const float* table, int size, float maxFreq, LEAF* const leaf);
+    void    tWavetable_initToPool(tWavetable* const osc, const float* table, int size, float maxFreq, tMempool* const mempool);
     void    tWavetable_free(tWavetable* const osc);
     
     float   tWavetable_tick(tWavetable* const osc);
     void    tWavetable_setFreq(tWavetable* const osc, float freq);
+    void    tWavetable_setAntiAliasing(tWavetable* const osc, float aa);
+    
+    //==============================================================================
+    
+    /*!
+     @defgroup tcompactwavetable tCompactWavetable
+     @ingroup oscillators
+     @brief A more space-efficient anti-aliased wavetable oscillator than tWavetable but with slightly worse fidelity.
+     @{
+     
+     @fn void    tCompactWavetable_init  (tCompactWavetable* const osc, float* table, int size, LEAF* const leaf)
+     @brief Initialize a tCompactWavetable to the default mempool of a LEAF instance.
+     @param osc A pointer to the tCompactWavetable to initialize.
+     @param table A pointer to the wavetable data.
+     @param size The number of samples in the wavetable.
+     @param leaf A pointer to the leaf instance.
+     
+     @fn void    tCompactWavetable_initToPool   (tCompactWavetable* const osc, float* table, int size, tMempool* const mempool)
+     @brief Initialize a tCompactWavetable to a specified mempool.
+     @param osc A pointer to the tCompactWavetable to initialize.
+     @param table A pointer to the wavetable data.
+     @param size The number of samples in the wave table.
+     @param mempool A pointer to the tMempool to use.
+     
+     @fn void    tCompactWavetable_free         (tCompactWavetable* const osc)
+     @brief Free a tCompactWavetable from its mempool.
+     @param osc A pointer to the tCompactWavetable to free.
+     
+     @fn float   tCompactWavetable_tick         (tCompactWavetable* const osc)
+     @brief Tick a tCompactWavetable oscillator.
+     @param osc A pointer to the relevant tCompactWavetable.
+     @return The ticked sample as a float from -1 to 1.
+     
+     @fn void    tCompactWavetable_setFreq      (tCompactWavetable* const osc, float freq)
+     @brief Set the frequency of a tCompactWavetable oscillator.
+     @param osc A pointer to the relevant tCompactWavetable.
+     @param freq The frequency to set the oscillator to.
+     
+     @} */
+    
+    typedef struct _tCompactWavetable
+    {
+        tMempool mempool;
+        
+        float** tables;
+        int numTables;
+        int* sizes;
+        float baseFreq, invBaseFreq;
+        float inc, freq;
+        float phase;
+        
+        int oct;
+        float w;
+        float aa;
+        
+        tButterworth bl;
+        
+        float dsBuffer[2];
+        tOversampler ds;
+    } _tCompactWavetable;
+    
+    typedef _tCompactWavetable* tCompactWavetable;
+    
+    void    tCompactWavetable_init(tCompactWavetable* const osc, const float* table, int size, float maxFreq, LEAF* const leaf);
+    void    tCompactWavetable_initToPool(tCompactWavetable* const osc, const float* table, int size, float maxFreq, tMempool* const mempool);
+    void    tCompactWavetable_free(tCompactWavetable* const osc);
+    
+    float   tCompactWavetable_tick(tCompactWavetable* const osc);
+    void    tCompactWavetable_setFreq(tCompactWavetable* const osc, float freq);
+    void    tCompactWavetable_setAntiAliasing(tCompactWavetable* const osc, float aa);
     
     //==============================================================================
     
--- a/leaf/Src/leaf-filters.c
+++ b/leaf/Src/leaf-filters.c
@@ -1058,9 +1058,9 @@
     for(int i = 0; i < f->order; ++i)
     {
         if (f1 >= 0.0f)
-            tSVF_initToPool(&f->svf[i], SVFTypeHighpass, f1, 0.5f/cosf((1.0f+2.0f*i)*PI/(2*f->order)), mp);
+            tSVF_initToPool(&f->svf[i], SVFTypeHighpass, f1, 0.5f/cosf((1.0f+2.0f*i)*PI/(4*f->order)), mp);
         if (f2 >= 0.0f)
-            tSVF_initToPool(&f->svf[i+o], SVFTypeLowpass, f2, 0.5f/cosf((1.0f+2.0f*i)*PI/(2*f->order)), mp);
+            tSVF_initToPool(&f->svf[i+o], SVFTypeLowpass, f2, 0.5f/cosf((1.0f+2.0f*i)*PI/(4*f->order)), mp);
     }
 }
 
@@ -1071,6 +1071,7 @@
     for(int i = 0; i < f->numSVF; ++i)
         tSVF_free(&f->svf[i]);
     
+    mpool_free((char*)f->svf, f->mempool);
     mpool_free((char*)f, f->mempool);
 }
 
--- a/leaf/Src/leaf-oscillators.c
+++ b/leaf/Src/leaf-oscillators.c
@@ -86,15 +86,15 @@
     c->inc = c->freq * leaf->invSampleRate;
 }
 
-void    tWavetable_init(tWavetable* const cy, float* table, int size, LEAF* const leaf)
+void tWavetable_init(tWavetable* const cy, const float* table, int size, float maxFreq, LEAF* const leaf)
 {
-    tWavetable_initToPool(cy, table, size, &leaf->mempool);
+    tWavetable_initToPool(cy, table, size, maxFreq, &leaf->mempool);
 }
 
-void    tWavetable_initToPool(tWavetable* const cy, float* table, int size, tMempool* const mp)
+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(_tTable), m);
+    _tWavetable* c = *cy = (_tWavetable*) mpool_alloc(sizeof(_tWavetable), m);
     c->mempool = m;
     
     // Determine base frequency
@@ -102,12 +102,13 @@
     c->invBaseFreq = 1.0f / c->baseFreq;
     
     // Determine how many tables we need
-    c->numTables = 1;
+    // 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 < 20000.f)
+    while (f < maxFreq)
     {
         c->numTables++;
-        f *= 2.0f; // pass this multiplier in to set spacing of tables?
+        f *= 2.0f; // pass this multiplier in to set spacing of tables? would need to change setFreq too
     }
     
     c->size = size;
@@ -125,26 +126,36 @@
         c->tables[0][i] = table[i];
     }
     
-    // Make bandlimited copies at
-    f = m->leaf->sampleRate * 0.25f; //start at half nyquist
+    // 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_initToPool(&c->bl, 4, -1.0f, f, mp);
-        for (int i = 0; i < c->size; ++i)
+        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)
         {
-            c->tables[t][i] = tButterworth_tick(&c->bl, table[i]);
+            for (int i = 0; i < c->size; ++i)
+            {
+                c->tables[t][i] = tButterworth_tick(&c->bl, c->tables[t-1][i]);
+            }
         }
-        tButterworth_free(&c->bl);
-        f *= 0.5f; //half the cutoff for next pass
+        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)
+void tWavetable_free(tWavetable* const cy)
 {
     _tWavetable* c = *cy;
     
@@ -153,17 +164,16 @@
         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)
+float tWavetable_tick(tWavetable* const cy)
 {
     _tWavetable* c = *cy;
     
     float temp;
     int idx;
-    float fracPart;
+    float frac;
     float samp0;
     float samp1;
     
@@ -174,18 +184,26 @@
     
     // Wavetable synthesis
     temp = c->size * c->phase;
+    
     idx = (int)temp;
-    fracPart = temp - (float)idx;
-    samp0 = c->tables[c->oct+1][idx] +
-    (c->tables[c->oct][idx] - c->tables[c->oct+1][idx]) * c->w;
+    frac = temp - (float)idx;
+    samp0 = c->tables[c->oct][idx];
     if (++idx >= c->size) idx = 0;
-    samp1 = c->tables[c->oct+1][idx] +
-    (c->tables[c->oct][idx] - c->tables[c->oct+1][idx]) * c->w;
+    samp1 = c->tables[c->oct][idx];
     
-    return (samp0 + (samp1 - samp0) * fracPart);
+    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)
+void tWavetable_setFreq(tWavetable* const cy, float freq)
 {
     _tWavetable* c = *cy;
     
@@ -195,14 +213,175 @@
     
     c->inc = c->freq * leaf->invSampleRate;
     
-    c->w = c->freq * c->invBaseFreq;
-    for (c->oct = 0; c->w > 2.0f; c->oct++)
+    // 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->w = 0.5f * c->w;
+        c->numTables++;
+        f *= 2.0f; // pass this multiplier in to set spacing of tables?
     }
-    c->w = 2.0f - c->w;
+        
+    // 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;
+    while (c->phase >= 1.0f) c->phase -= 1.0f;
+    while (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;
+    
+    // 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)
@@ -243,9 +422,9 @@
 float   tCycle_tick(tCycle* const cy)
 {
     _tCycle* c = *cy;
-    float temp;;
-    int intPart;;
-    float fracPart;
+    float temp;
+    int idx;
+    float frac;
     float samp0;
     float samp1;
     
@@ -257,13 +436,13 @@
     // Wavetable synthesis
 
     temp = SINE_TABLE_SIZE * c->phase;
-    intPart = (int)temp;
-    fracPart = temp - (float)intPart;
-    samp0 = __leaf_table_sinewave[intPart];
-    if (++intPart >= SINE_TABLE_SIZE) intPart = 0;
-    samp1 = __leaf_table_sinewave[intPart];
+    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) * fracPart);
+    return (samp0 + (samp1 - samp0) * frac);
 }
 
 void     tCycleSampleRateChanged (tCycle* const cy)
@@ -310,12 +489,14 @@
     
     c->inc = c->freq * leaf->invSampleRate;
     
-    c->w = c->freq * INV_20;
-    for (c->oct = 0; c->w > 2.0f; c->oct++)
-    {
-        c->w = 0.5f * c->w;
-    }
-    c->w = 2.0f - c->w;
+    // 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;
 }
 
 
@@ -323,20 +504,36 @@
 {
     _tTriangle* 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 = TRI_TABLE_SIZE * c->phase;
     
-    float out = 0.0f;
+    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];
     
-    int idx = (int)(c->phase * TRI_TABLE_SIZE);
+    float oct0 = (samp0 + (samp1 - samp0) * frac);
     
-    // Wavetable synthesis
-    out = __leaf_table_triangle[c->oct+1][idx] +
-         (__leaf_table_triangle[c->oct][idx] - __leaf_table_triangle[c->oct+1][idx]) * c->w;
+    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];
     
-    return out;
+    float oct1 = (samp0 + (samp1 - samp0) * frac);
+    
+    return oct0 + (oct1 - oct0) * c->w;
 }
 
 void     tTriangleSampleRateChanged (tTriangle* const cy)
@@ -383,12 +580,14 @@
     
     c->inc = c->freq * leaf->invSampleRate;
     
-    c->w = c->freq * INV_20;
-    for (c->oct = 0; c->w > 2.0f; c->oct++)
-    {
-        c->w = 0.5f * c->w;
-    }
-    c->w = 2.0f - c->w;
+    // 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)
@@ -395,20 +594,36 @@
 {
     _tSquare* 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 = SQR_TABLE_SIZE * c->phase;
     
-    float out = 0.0f;
+    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];
     
-    int idx = (int)(c->phase * SQR_TABLE_SIZE);
+    float oct0 = (samp0 + (samp1 - samp0) * frac);
     
-    // 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;
+    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];
     
-    return out;
+    float oct1 = (samp0 + (samp1 - samp0) * frac);
+    
+    return oct0 + (oct1 - oct0) * c->w;
 }
 
 void     tSquareSampleRateChanged (tSquare* const cy)
@@ -455,13 +670,14 @@
     
     c->inc = c->freq * leaf->invSampleRate;
     
-    // base freq 20
-    c->w = c->freq * INV_20;
-    for (c->oct = 0; c->w > 2.0f; c->oct++)
-    {
-        c->w = 0.5f * c->w;
-    }
-    c->w = 2.0f - c->w;
+    // 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)
@@ -468,20 +684,36 @@
 {
     _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;
     
-    float out = 0.0f;
+    // Wavetable synthesis
+    temp = SAW_TABLE_SIZE * c->phase;
     
-    int idx = (int)(c->phase * SAW_TABLE_SIZE);
+    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];
     
-    // Wavetable synthesis
-    out = __leaf_table_sawtooth[c->oct+1][idx] +
-        (__leaf_table_sawtooth[c->oct][idx] - __leaf_table_sawtooth[c->oct+1][idx]) * c->w;
+    float oct0 = (samp0 + (samp1 - samp0) * frac);
     
-    return out;
+    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)
--- a/leaf/leaf-config.h
+++ b/leaf/leaf-config.h
@@ -18,7 +18,7 @@
 
 //==============================================================================
 
-//! Include FIR tables required to use tOversampler.
+//! Include FIR tables required to use tOversampler and tCompactWavetable which uses tOversampler. 
 #define LEAF_INCLUDE_OVERSAMPLER_TABLES 1
 
 // Unused