shithub: leaf

Download patch

ref: 3152c9f2d0d1dd1de3c1025ce7f924242ff2ca82
parent: 697a53f9c9921991681e2a599af8b163d8dc388a
author: Matthew Wang <mjw7@princeton.edu>
date: Wed Aug 12 13:11:34 EDT 2020

tDualPitchDetector and other bacf pitch detector updates

--- a/TestPlugin/Source/MyTest.cpp
+++ b/TestPlugin/Source/MyTest.cpp
@@ -18,14 +18,18 @@
 tMBTriangle btri;
 tMBPulse bpulse;
 
-tPitchDetector detector;
+tDualPitchDetector detector;
 
+tCompressor compressor;
+
+tSVF lp, hp;
+
 tPeriodDetection pd;
 
 tZeroCrossingCounter zc;
 tEnvelopeFollower ef;
 
-tCycle sine;
+tTriangle tri;
 
 tBuffer samp;
 tMBSampler sampler;
@@ -54,16 +58,21 @@
     
     bufIn = (float*) leaf_alloc(sizeof(float) * 4096);
     bufOut = (float*) leaf_alloc(sizeof(float) * 4096);
-    // lowestFreq, highestFreq, hysteresis (width of hysteresis region around 0.0 for zero crossing detection)
-    tPitchDetector_init(&detector, mtof(48), mtof(84), 0.01f);
     
+    tDualPitchDetector_init(&detector, mtof(53), mtof(77));
+    
+    tCompressor_init(&compressor);
+    
+    tSVF_init(&lp, SVFTypeLowpass, mtof(77) * 2.0f, 1.0f);
+    tSVF_init(&hp, SVFTypeHighpass, mtof(48) * 0.5f, 1.0f);
+    
     tPeriodDetection_init(&pd, bufIn, bufOut, 4096, 1024);
     
     tZeroCrossingCounter_init(&zc, 128);
     tEnvelopeFollower_init(&ef, 0.02f, 0.9999f);
     
-    tCycle_init(&sine);
-    tCycle_setFreq(&sine, 100);
+    tTriangle_init(&tri);
+    tTriangle_setFreq(&tri, 100);
     
     tBuffer_init(&samp, 5.0f * leaf.sampleRate);
     tMBSampler_init(&sampler, &samp);
@@ -81,14 +90,13 @@
     
 }
 
-float lastFreq;
 float   LEAFTest_tick            (float input)
 {
-    tBuffer_tick(&samp, input);
+//    tBuffer_tick(&samp, input);
+//
+//    return tMBSampler_tick(&sampler);
     
-    return tMBSampler_tick(&sampler);
     
-    
 //    tMBSaw_setFreq(&bsaw, x);
 //    tMBTriangle_setFreq(&btri, x);
 //    tMBPulse_setFreq(&bpulse, x);
@@ -99,23 +107,23 @@
 ////    return tMBSaw_tick(&bsaw);
 ////    return tMBTriangle_tick(&btri);
 //    return tMBPulse_tick(&bpulse);
-
     
+    input = tSVF_tick(&hp, tSVF_tick(&lp, tCompressor_tick(&compressor, input)));
+    
 //    float freq = 1.0f/tPeriodDetection_tick(&pd, input) * leaf.sampleRate;
-//    tPitchDetector_tick(&detector, input);
-//    float altFreq = tPitchDetector_getFrequency(&detector);
-//
+    tDualPitchDetector_tick(&detector, input);
+    float altFreq = tDualPitchDetector_getFrequency(&detector);
+
 //    if (fabsf(1.0f - (freq / altFreq)) < 0.05f)
-////    if (tZeroCrossingCounter_tick(&zc, input) < 0.05 && freq > 0.0f)
-//    {
-//        tCycle_setFreq(&sine, altFreq);
-//    }
-//
-//    float g = tEnvelopeFollower_tick(&ef, input);
-//
-//    lastFreq = freq;
-//
-//    return tCycle_tick(&sine) * g * 2.0f + input;
+//    if (tZeroCrossingCounter_tick(&zc, input) < 0.05 && freq > 0.0f)
+    if (altFreq > 0.0f)
+    {
+        tTriangle_setFreq(&tri, altFreq);
+    }
+
+    float g = tEnvelopeFollower_tick(&ef, input);
+
+    return tTriangle_tick(&tri) * g;
 }
 
 int firstFrame = 1;
@@ -122,11 +130,11 @@
 bool lastState = false, lastPlayState = false;
 void    LEAFTest_block           (void)
 {
-    float periodicity = tPitchDetector_getPeriodicity(&detector);
+    float periodicity = tDualPitchDetector_getPeriodicity(&detector);
     if (periodicity > 0.99f)
     {
-        DBG(tPitchDetector_getFrequency(&detector));
-        DBG(tPitchDetector_getPeriodicity(&detector));
+        DBG(tDualPitchDetector_getFrequency(&detector));
+        DBG(tDualPitchDetector_getPeriodicity(&detector));
     }
     
     float val = getSliderValue("on/off");
--- a/leaf/Inc/leaf-analysis.h
+++ b/leaf/Inc/leaf-analysis.h
@@ -572,13 +572,9 @@
     int     tZeroCrossingInfo_period(tZeroCrossingInfo* const, tZeroCrossingInfo* const next);
     float   tZeroCrossingInfo_fractionalPeriod(tZeroCrossingInfo* const, tZeroCrossingInfo* const next);
     int     tZeroCrossingInfo_getWidth(tZeroCrossingInfo* const);
-    int     tZeroCrossingInfo_isSimilar(tZeroCrossingInfo* const, tZeroCrossingInfo* const next);
     
     //==============================================================================
     
-#define PULSE_HEIGHT_DIFF 0.8
-#define PULSE_WIDTH_DIFF 0.85
-    
     typedef struct _tZeroCrossingCollector
     {
         tMempool mempool;
@@ -618,6 +614,8 @@
     int     tZeroCrossingCollector_isReset(tZeroCrossingCollector* const zc);
     
     tZeroCrossingInfo const tZeroCrossingCollector_getCrossing(tZeroCrossingCollector* const zc, int index);
+
+    void    tZeroCrossingCollector_setHysteresis(tZeroCrossingCollector* const zc, float hysteresis);
     
     //==============================================================================
     
@@ -709,7 +707,16 @@
      @fn int     tPeriodDetector_isReady (tPeriodDetector* const detector)
      @brief
      @param
+     
+     @fn int     tPeriodDetector_isReset (tPeriodDetector* const detector)
+     @brief
+     @param
      
+     @fn void    tPeriodDetector_setHysteresis    (tPeriodDetector* const detector, float hysteresis)
+     @brief Set the hysteresis used in zero crossing detection.
+     @param detector A pointer to the relevant tPeriodDetector.
+     @param hysteresis The hysteresis in decibels. Defaults to -40db.
+     
      @} */
     
 #define PULSE_THRESHOLD 0.6f
@@ -741,12 +748,18 @@
         int               _range;
     } _sub_collector;
 
+    typedef struct _period_info
+    {
+        float period; // -1.0f
+        float periodicity;
+    } _period_info;
+
     typedef struct _tPeriodDetector
     {
         tMempool mempool;
         
         tZeroCrossingCollector          _zc;
-        float                   _period_info[2]; // i0 is period, i1 is periodicity
+        _period_info            _fundamental;
         unsigned int            _min_period;
         int                     _range;
         tBitset                 _bits;
@@ -756,6 +769,8 @@
         float                   _predicted_period;// = -1.0f;
         unsigned int            _edge_mark;// = 0;
         unsigned int            _predict_edge;// = 0;
+        unsigned int            _num_pulses; // = 0;
+        int                     _half_empty; // 0;
         
         tBACF                   _bacf;
         
@@ -775,6 +790,9 @@
     float   tPeriodDetector_harmonic    (tPeriodDetector* const detector, int harmonicIndex);
     float   tPeriodDetector_predictPeriod   (tPeriodDetector* const detector);
     int     tPeriodDetector_isReady (tPeriodDetector* const detector);
+    int     tPeriodDetector_isReset (tPeriodDetector* const detector);
+
+    void    tPeriodDetector_setHysteresis   (tPeriodDetector* const detector, float hysteresis);
     
     //==============================================================================
     
@@ -784,46 +802,60 @@
      @brief
      @{
      
-     @fn void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis)
+     @fn void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq)
      @brief Initialize a tPitchDetector to the default LEAF mempool.
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
+     @param lowestFreq
+     @param highestFreq
      
-     @fn void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis, tMempool* const mempool)
+     
+     @fn void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, tMempool* const mempool)
      @brief Initialize a tPitchDetector to a specified mempool.
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
+     @param lowestFreq
+     @param highestFreq
+     @param mempool
      
      @fn void    tPitchDetector_free (tPitchDetector* const detector)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
      @fn int     tPitchDetector_tick    (tPitchDetector* const detector, float sample)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
      @fn float   tPitchDetector_getFrequency    (tPitchDetector* const detector)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
      @fn float   tPitchDetector_getPeriodicity  (tPitchDetector* const detector)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
      @fn float   tPitchDetector_harmonic    (tPitchDetector* const detector, int harmonicIndex)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
-     @fn float   tPitchDetector_predictFrequency (tPitchDetector* const detector, int init)
+     @fn float   tPitchDetector_predictFrequency (tPitchDetector* const detector)
      @brief
-     @param
+     @param detector A pointer to the relevant tPitchDetector.
      
-     @fn void    tPitchDetector_reset    (tPitchDetector* const detector)
-     @brief
-     @param
+     @fn void    tPitchDetector_setHysteresis    (tPitchDetector* const detector, float hysteresis)
+     @brief Set the hysteresis used in zero crossing detection.
+     @param detector A pointer to the relevant tPitchDetector.
+     @param hysteresis The hysteresis in decibels. Defaults to -40db.
      
      @} */
     
-#define MAX_DEVIATION 0.9f
-#define MIN_PERIODICITY 0.8f
+#define ONSET_PERIODICITY 0.95f
+#define MIN_PERIODICITY 0.9f
+#define DEFAULT_HYSTERESIS -40.0f
+
+    typedef struct _pitch_info
+    {
+        float frequency;
+        float periodicity;
+    } _pitch_info;
     
     typedef struct _tPitchDetector
     {
@@ -830,9 +862,7 @@
         tMempool mempool;
         
         tPeriodDetector _pd;
-        float _frequency;
-        float _median, _median_b, _median_c;
-        float _predict_median, _predict_median_b, _predict_median_c;
+        _pitch_info _current;
         int _frames_after_shift;// = 0;
         
     } _tPitchDetector;
@@ -839,8 +869,8 @@
     
     typedef _tPitchDetector* tPitchDetector;
     
-    void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis);
-    void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis, tMempool* const mempool);
+    void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq);
+    void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, tMempool* const mempool);
     void    tPitchDetector_free (tPitchDetector* const detector);
     
     int     tPitchDetector_tick    (tPitchDetector* const detector, float sample);
@@ -847,16 +877,43 @@
     float   tPitchDetector_getFrequency    (tPitchDetector* const detector);
     float   tPitchDetector_getPeriodicity  (tPitchDetector* const detector);
     float   tPitchDetector_harmonic    (tPitchDetector* const detector, int harmonicIndex);
-    float   tPitchDetector_predictFrequency (tPitchDetector* const detector, int init);
-    void    tPitchDetector_reset    (tPitchDetector* const detector);
+    float   tPitchDetector_predictFrequency (tPitchDetector* const detector);
+    int     tPitchDetector_indeterminate    (tPitchDetector* const detector);
     
+    void    tPitchDetector_setHysteresis    (tPitchDetector* const detector, float hysteresis);
     
     
+
+    typedef struct _tDualPitchDetector
+    {
+        tMempool mempool;
+        
+        tPitchDetector _pd1;
+        tPitchDetector _pd2;
+        _pitch_info _current;
+        float _mean;
+        float _predicted_frequency;
+        int _first;
+        
+    } _tDualPitchDetector;
     
+    typedef _tDualPitchDetector* tDualPitchDetector;
     
+    void    tDualPitchDetector_init (tDualPitchDetector* const detector, float lowestFreq, float highestFreq);
+    void    tDualPitchDetector_initToPool   (tDualPitchDetector* const detector, float lowestFreq, float highestFreq, tMempool* const mempool);
+    void    tDualPitchDetector_free (tDualPitchDetector* const detector);
     
+    int     tDualPitchDetector_tick    (tDualPitchDetector* const detector, float sample);
+    float   tDualPitchDetector_getFrequency    (tDualPitchDetector* const detector);
+    float   tDualPitchDetector_getPeriodicity  (tDualPitchDetector* const detector);
+    float   tDualPitchDetector_harmonic    (tDualPitchDetector* const detector, int harmonicIndex);
+    float   tDualPitchDetector_predictFrequency (tDualPitchDetector* const detector);
     
+    void    tDualPitchDetector_setHysteresis    (tDualPitchDetector* const detector, float hysteresis);
     
+    
+    
+    
 #ifdef __cplusplus
 }
 #endif
@@ -864,6 +921,7 @@
 #endif  // LEAF_ANALYSIS_H_INCLUDED
 
 //==============================================================================
+
 
 
 
--- a/leaf/Src/leaf-analysis.c
+++ b/leaf/Src/leaf-analysis.c
@@ -1021,19 +1021,6 @@
     return z->_width;
 }
 
-int     tZeroCrossingInfo_isSimilar(tZeroCrossingInfo* const zc, tZeroCrossingInfo* const next)
-{
-    _tZeroCrossingInfo* z = *zc;
-    _tZeroCrossingInfo* n = *next;
-    
-    int similarPeak = fabs(z->_peak - n->_peak) <= ((1.0f - PULSE_HEIGHT_DIFF) * fmax(fabs(z->_peak), fabs(n->_peak)));
-    
-    int similarWidth = fabs(z->_width - n->_width) <= ((1.0f - PULSE_WIDTH_DIFF) * fmax(fabs(z->_width), fabs(n->_width)));
-    
-    return similarPeak && similarWidth;
-}
-
-
 static inline void update_state(tZeroCrossingCollector* const zc, float s);
 static inline void shift(tZeroCrossingCollector* const zc, int n);
 static inline void reset(tZeroCrossingCollector* const zc);
@@ -1049,7 +1036,7 @@
     _tZeroCrossingCollector* z = *zc = (_tZeroCrossingCollector*) mpool_alloc(sizeof(_tZeroCrossingCollector), m);
     z->mempool = m;
     
-    z->_hysteresis = -hysteresis;
+    z->_hysteresis = -dbtoa(hysteresis);
     int bits = CHAR_BIT * sizeof(unsigned int);
     z->_window_size = fmax(2, (windowSize + bits - 1) / bits) * bits;
     
@@ -1177,6 +1164,13 @@
     return z->_frame == 0;
 }
 
+void    tZeroCrossingCollector_setHysteresis(tZeroCrossingCollector* const zc, float hysteresis)
+{
+    _tZeroCrossingCollector* z = *zc;
+    
+    z->_hysteresis = -dbtoa(hysteresis);
+}
+
 static inline void update_state(tZeroCrossingCollector* const zc, float s)
 {
     _tZeroCrossingCollector* z = *zc;
@@ -1225,6 +1219,9 @@
             z->_peak = z->_peak_update;
     }
     
+    if (z->_frame > z->_window_size * 2)
+        reset(zc);
+    
     z->_prev = s;
 }
 
@@ -1494,10 +1491,10 @@
 static inline void sub_collector_init(_sub_collector* collector, tZeroCrossingCollector* const crossings, float pdt, int range);
 static inline float sub_collector_period_of(_sub_collector* collector, _auto_correlation_info info);
 static inline void sub_collector_save(_sub_collector* collector, _auto_correlation_info info);
-static inline int sub_collector_try_sub_harmonic(_sub_collector* collector, int harmonic, _auto_correlation_info info);
+static inline int sub_collector_try_sub_harmonic(_sub_collector* collector, int harmonic, _auto_correlation_info info, float incoming_period);
 static inline int sub_collector_process_harmonics(_sub_collector* collector, _auto_correlation_info info);
 static inline void sub_collector_process(_sub_collector* collector, _auto_correlation_info info);
-static inline void sub_collector_get(_sub_collector* collector, _auto_correlation_info info, float* result);
+static inline void sub_collector_get(_sub_collector* collector, _auto_correlation_info info, _period_info* result);
 
 void    tPeriodDetector_init    (tPeriodDetector* const detector, float lowestFreq, float highestFreq, float hysteresis)
 {
@@ -1523,6 +1520,8 @@
     p->_predicted_period = -1.0f;
     p->_edge_mark = 0;
     p->_predict_edge = 0;
+    p->_num_pulses = 0;
+    p->_half_empty = 0;
     
     tBACF_initToPool(&p->_bacf, &p->_bits, mempool);
 }
@@ -1554,8 +1553,8 @@
     
     if (tZeroCrossingCollector_isReset(&p->_zc))
     {
-        p->_period_info[0] = -1.0f;
-        p->_period_info[1] = 0.0f;
+        p->_fundamental.period = -1.0f;
+        p->_fundamental.periodicity = 0.0f;
     }
     
     if (tZeroCrossingCollector_isReady(&p->_zc))
@@ -1571,7 +1570,7 @@
 {
     _tPeriodDetector* p = *detector;
     
-    return p->_period_info[0];
+    return p->_fundamental.period;
 }
 
 float   tPeriodDetector_getPeriodicity  (tPeriodDetector* const detector)
@@ -1578,7 +1577,7 @@
 {
     _tPeriodDetector* p = *detector;
     
-    return p->_period_info[1];
+    return p->_fundamental.periodicity;
 }
 
 float   tPeriodDetector_harmonic    (tPeriodDetector* const detector, int harmonicIndex)
@@ -1588,9 +1587,9 @@
     if (harmonicIndex > 0)
     {
         if (harmonicIndex == 1)
-            return p->_period_info[1];
+            return p->_fundamental.periodicity;
         
-        float target_period = p->_period_info[0] / (float) harmonicIndex;
+        float target_period = p->_fundamental.period / (float) harmonicIndex;
         if (target_period >= p->_min_period && target_period < p->_mid_point)
         {
             int count = tBACF_getCorrelation(&p->_bacf, roundf(target_period));
@@ -1620,12 +1619,14 @@
                     for (int j = i-1; j >= 0; --j)
                     {
                         tZeroCrossingInfo edge1 = tZeroCrossingCollector_getCrossing(&p->_zc, j);
-                        if (tZeroCrossingInfo_isSimilar(&edge1, &edge2))
+                        if (edge1->_peak >= threshold)
                         {
-                            p->_predicted_period = tZeroCrossingInfo_fractionalPeriod(&edge1, &edge2);
-                            return p->_predicted_period;
+                            float period = tZeroCrossingInfo_fractionalPeriod(&edge1, &edge2);
+                            if (period > p->_min_period)
+                                return (p->_predicted_period = period);
                         }
                     }
+                    return p->_predicted_period = -1.0f;
                 }
             }
         }
@@ -1640,24 +1641,47 @@
     return tZeroCrossingCollector_isReady(&p->_zc);
 }
 
+int     tPeriodDetector_isReset (tPeriodDetector* const detector)
+{
+    _tPeriodDetector* p = *detector;
+    
+    return tZeroCrossingCollector_isReset(&p->_zc);
+}
+
+void    tPeriodDetector_setHysteresis   (tPeriodDetector* const detector, float hysteresis)
+{
+    _tPeriodDetector* p = *detector;
+    
+    return tZeroCrossingCollector_setHysteresis(&p->_zc, hysteresis);
+}
+
 static inline void set_bitstream(tPeriodDetector* const detector)
 {
     _tPeriodDetector* p = *detector;
     
     float threshold = tZeroCrossingCollector_getPeak(&p->_zc) * PULSE_THRESHOLD;
+    unsigned int leading_edge = tZeroCrossingCollector_getWindowSize(&p->_zc);
+    unsigned int trailing_edge = 0;
     
+    p->_num_pulses = 0;
     tBitset_clear(&p->_bits);
-
+    
     for (int i = 0; i != tZeroCrossingCollector_getNumEdges(&p->_zc); ++i)
     {
         tZeroCrossingInfo info = tZeroCrossingCollector_getCrossing(&p->_zc, i);
         if (info->_peak >= threshold)
         {
+            ++p->_num_pulses;
+            if (info->_leading_edge < leading_edge)
+                leading_edge = info->_leading_edge;
+            if (info->_trailing_edge > trailing_edge)
+                trailing_edge = info->_trailing_edge;
             int pos = fmax(info->_leading_edge, 0);
             int n = info->_trailing_edge - pos;
             tBitset_setMultiple(&p->_bits, pos, n, 1);
         }
     }
+    p->_half_empty = (leading_edge > p->_mid_point) || (trailing_edge < p->_mid_point);
 }
 
 static inline void autocorrelate(tPeriodDetector* const detector)
@@ -1669,69 +1693,87 @@
     _sub_collector collect;
     sub_collector_init(&collect, &p->_zc, p->_periodicity_diff_threshold, p->_range);
     
-    int shouldBreak = 0;
-    int n = tZeroCrossingCollector_getNumEdges(&p->_zc);
-    for (int i = 0; i != n - 1; ++i)
+    if (p->_half_empty || p->_num_pulses < 2)
     {
-        tZeroCrossingInfo first = tZeroCrossingCollector_getCrossing(&p->_zc, i);
-        if (first->_peak >= threshold)
+        p->_fundamental.periodicity = -1.0f;
+        return;
+    }
+    else
+    {
+        int shouldBreak = 0;
+        int n = tZeroCrossingCollector_getNumEdges(&p->_zc);
+        for (int i = 0; i != n - 1; ++i)
         {
-            for (int j = i + 1; j != n; ++j)
+            tZeroCrossingInfo curr = tZeroCrossingCollector_getCrossing(&p->_zc, i);
+            if (curr->_peak >= threshold)
             {
-                tZeroCrossingInfo next = tZeroCrossingCollector_getCrossing(&p->_zc, j);
-                if (next->_peak >= threshold)
+                for (int j = i + 1; j != n; ++j)
                 {
-                    int period = tZeroCrossingInfo_period(&first, &next);
-                    if (period > p->_mid_point)
-                        break;
-                    if (period >= p->_min_period)
+                    tZeroCrossingInfo next = tZeroCrossingCollector_getCrossing(&p->_zc, j);
+                    if (next->_peak >= threshold)
                     {
-                        
-                        int count = tBACF_getCorrelation(&p->_bacf, period);
-                        
-                        int mid = p->_bacf->_mid_array * CHAR_BIT * sizeof(unsigned int);
-                        
-                        int start = period;
-                        
-                        if (period < 32) // Search minimum if the resolution is low
+                        int period = tZeroCrossingInfo_period(&curr, &next);
+                        if (period > p->_mid_point)
+                            break;
+                        if (period >= p->_min_period)
                         {
-                            // Search upwards for the minimum autocorrelation count
-                            for (int d = start + 1; d < mid; ++d)
+                            
+                            int count = tBACF_getCorrelation(&p->_bacf, period);
+                            
+                            int mid = p->_bacf->_mid_array * CHAR_BIT * sizeof(unsigned int);
+                            
+                            int start = period;
+                            
+                            if ((collect._fundamental._period == -1.0f) && count == 0)
                             {
-                                int c = tBACF_getCorrelation(&p->_bacf, d);
-                                if (c > count)
-                                    break;
-                                count = c;
-                                period = d;
+                                if (tBACF_getCorrelation(&p->_bacf, period / 2.0f) == 0)
+                                    count = -1;
                             }
-                            // Search downwards for the minimum autocorrelation count
-                            for (int d = start - 1; d > p->_min_period; --d)
+                            else if (period < 32) // Search minimum if the resolution is low
                             {
-                                int c = tBACF_getCorrelation(&p->_bacf, d);
-                                if (c > count)
-                                    break;
-                                count = c;
-                                period = d;
+                                // Search upwards for the minimum autocorrelation count
+                                for (int d = start + 1; d < mid; ++d)
+                                {
+                                    int c = tBACF_getCorrelation(&p->_bacf, d);
+                                    if (c > count)
+                                        break;
+                                    count = c;
+                                    period = d;
+                                }
+                                // Search downwards for the minimum autocorrelation count
+                                for (int d = start - 1; d > p->_min_period; --d)
+                                {
+                                    int c = tBACF_getCorrelation(&p->_bacf, d);
+                                    if (c > count)
+                                        break;
+                                    count = c;
+                                    period = d;
+                                }
                             }
+                            
+                            if (count == -1)
+                            {
+                                shouldBreak = 1;
+                                break; // Return early if we have false correlation
+                            }
+                            float periodicity = 1.0f - (count * p->_weight);
+                            _auto_correlation_info info = { i, j, (int) period, periodicity };
+                            sub_collector_process(&collect, info);
+                            if (count == 0)
+                            {
+                                shouldBreak = 1;
+                                break; // Return early if we have perfect correlation
+                            }
                         }
-                        
-                        float periodicity = 1.0f - (count * p->_weight);
-                        _auto_correlation_info info = { i, j, (int) period, periodicity };
-                        sub_collector_process(&collect, info);
-                        if (count == 0)
-                        {
-                            shouldBreak = 1;
-                            break; // Return early if we have perfect correlation
-                        }
                     }
                 }
             }
+            if (shouldBreak > 0) break;
         }
-        if (shouldBreak > 0) break;
     }
     
     // Get the final resuts
-    sub_collector_get(&collect, collect._fundamental, p->_period_info);
+    sub_collector_get(&collect, collect._fundamental, &p->_fundamental);
 }
 
 static inline void sub_collector_init(_sub_collector* collector, tZeroCrossingCollector* const crossings, float pdt, int range)
@@ -1761,11 +1803,9 @@
     collector->_first_period = sub_collector_period_of(collector, collector->_fundamental);
 }
 
-static inline int sub_collector_try_sub_harmonic(_sub_collector* collector, int harmonic, _auto_correlation_info info)
+static inline int sub_collector_try_sub_harmonic(_sub_collector* collector, int harmonic, _auto_correlation_info info, float incoming_period)
 {
-    int incoming_period = info._period / harmonic;
-    int current_period = collector->_fundamental._period;
-    if (abs(incoming_period - current_period) < collector->_periodicity_diff_threshold)
+    if (fabsf(incoming_period - collector->_first_period) < collector->_periodicity_diff_threshold)
     {
         // If incoming is a different harmonic and has better
         // periodicity ...
@@ -1792,18 +1832,19 @@
                 sub_collector_save(collector, info);
             }
         }
-        return true;
+        return 1;
     }
-    return false;
+    return 0;
 }
 
 static inline int sub_collector_process_harmonics(_sub_collector* collector, _auto_correlation_info info)
 {
     if (info._period < collector->_first_period)
-        return false;
-    
-    int multiple = fmaxf(1.0f, roundf(sub_collector_period_of(collector, info) / collector->_first_period));
-    return sub_collector_try_sub_harmonic(collector, fmin(collector->_range, multiple), info);
+        return 0;
+     
+    float incoming_period = sub_collector_period_of(collector, info);
+    int multiple = fmaxf(1.0f, roundf( incoming_period / collector->_first_period));
+    return sub_collector_try_sub_harmonic(collector, fmin(collector->_range, multiple), info, incoming_period/multiple);
 }
 
 static inline void sub_collector_process(_sub_collector* collector, _auto_correlation_info info)
@@ -1818,36 +1859,37 @@
         sub_collector_save(collector, info);
 }
 
-static inline void sub_collector_get(_sub_collector* collector, _auto_correlation_info info, float* result)
+static inline void sub_collector_get(_sub_collector* collector, _auto_correlation_info info, _period_info* result)
 {
     if (info._period != -1.0f)
     {
-        result[0] = sub_collector_period_of(collector, info) / info._harmonic;
-        result[1] = info._periodicity;
+        result->period = sub_collector_period_of(collector, info) / info._harmonic;
+        result->periodicity = info._periodicity;
     }
     else
     {
-        result[0] = -1.0f;
-        result[1] = 0.0f;
+        result->period = -1.0f;
+        result->period = 0.0f;
     }
 }
 
 static inline float calculate_frequency(tPitchDetector* const detector);
-static inline void bias(tPitchDetector* const detector, float incoming);
+static inline void bias(tPitchDetector* const detector, _pitch_info incoming);
 
-void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis)
+void    tPitchDetector_init (tPitchDetector* const detector, float lowestFreq, float highestFreq)
 {
-    tPitchDetector_initToPool(detector, lowestFreq, highestFreq, hysteresis, &leaf.mempool);
+    tPitchDetector_initToPool(detector, lowestFreq, highestFreq, &leaf.mempool);
 }
 
-void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, float hysteresis, tMempool* const mempool)
+void    tPitchDetector_initToPool   (tPitchDetector* const detector, float lowestFreq, float highestFreq, tMempool* const mempool)
 {
     _tMempool* m = *mempool;
     _tPitchDetector* p = *detector = (_tPitchDetector*) mpool_alloc(sizeof(_tPitchDetector), m);
     p->mempool = m;
     
-    tPeriodDetector_initToPool(&p->_pd, lowestFreq, highestFreq, hysteresis, mempool);
-    p->_frequency = 0.0f;
+    tPeriodDetector_initToPool(&p->_pd, lowestFreq, highestFreq, DEFAULT_HYSTERESIS, mempool);
+    p->_current.frequency = 0.0f;
+    p->_current.periodicity = 0.0f;
     p->_frames_after_shift = 0;
 }
 
@@ -1864,22 +1906,33 @@
     _tPitchDetector* p = *detector;
     tPeriodDetector_tick(&p->_pd, s);
     
+    if (tPeriodDetector_isReset(&p->_pd))
+    {
+        p->_current.frequency = 0.0f;
+        p->_current.periodicity = 0.0f;
+    }
+    
     int ready = tPeriodDetector_isReady(&p->_pd);
-    if (ready > 0)
+    if (ready)
     {
-        if (p->_frequency == 0.0f)
+        float periodicity = p->_pd->_fundamental.periodicity;
+        
+        if (periodicity == -1.0f)
         {
-            // Disregard if we are not periodic enough
-            if (p->_pd->_period_info[1] >= MAX_DEVIATION)
+            p->_current.frequency = 0.0f;
+            p->_current.periodicity = 0.0f;
+            return 0;
+        }
+        
+        if (p->_current.frequency == 0.0f)
+        {
+            if (periodicity >= ONSET_PERIODICITY)
             {
                 float f = calculate_frequency(detector);
                 if (f > 0.0f)
                 {
-                    // Apply the median for the future
-                    p->_median = median3f(f, p->_median_b, p->_median_c);
-                    p->_median_c = p->_median_b;
-                    p->_median_b = f;
-                    p->_frequency = f;   // But assign outright now
+                    p->_current.frequency = f;
+                    p->_current.periodicity = periodicity;
                     p->_frames_after_shift = 0;
                 }
             }
@@ -1886,11 +1939,14 @@
         }
         else
         {
-            if (p->_pd->_period_info[1] < MIN_PERIODICITY)
+            if (periodicity < MIN_PERIODICITY)
                 p->_frames_after_shift = 0;
             float f = calculate_frequency(detector);
             if (f > 0.0f)
-                bias(detector, f);
+            {
+                _pitch_info info = { f, periodicity };
+                bias(detector, info);
+            }
         }
     }
     return ready;
@@ -1900,7 +1956,7 @@
 {
     _tPitchDetector* p = *detector;
     
-    return p->_frequency;
+    return p->_current.frequency;
 }
 
 float   tPitchDetector_getPeriodicity  (tPitchDetector* const detector)
@@ -1907,7 +1963,7 @@
 {
     _tPitchDetector* p = *detector;
     
-    return p->_pd->_period_info[1];
+    return p->_current.periodicity;
 }
 
 float   tPitchDetector_harmonic (tPitchDetector* const detector, int harmonicIndex)
@@ -1917,82 +1973,75 @@
     return tPeriodDetector_harmonic(&p->_pd, harmonicIndex);
 }
 
-float   tPitchDetector_predictFrequency (tPitchDetector* const detector, int init)
+float   tPitchDetector_predictFrequency (tPitchDetector* const detector)
 {
     _tPitchDetector* p = *detector;
     
     float period = tPeriodDetector_predictPeriod(&p->_pd);
-    if (period < p->_pd->_min_period)
-        return 0.0f;
-    float f = leaf.sampleRate / period;
-    if (p->_frequency != f)
-    {
-        p->_predict_median = median3f(f, p->_predict_median_b, p->_predict_median_c);
-        p->_predict_median_c = p->_predict_median_b;
-        p->_predict_median_b = f;
-        f = p->_predict_median;
-        if (init > 0)
-        {
-            p->_median = median3f(f, p->_median_b, p->_median_c);
-            p->_median_c = p->_median_b;
-            p->_median_b = f;
-            p->_frequency = p->_median;
-        }
-    }
-    return f;
+    if (period > 0.0f)
+       return leaf.sampleRate / period;
+   return 0.0f;
 }
 
-void    tPitchDetector_reset    (tPitchDetector* const detector)
+int     tPitchDetector_indeterminate    (tPitchDetector* const detector)
 {
     _tPitchDetector* p = *detector;
     
-    p->_frequency = 0.0f;
+    return p->_current.frequency == 0.0f;
 }
 
+void    tPitchDetector_setHysteresis    (tPitchDetector* const detector, float hysteresis)
+{
+    _tPitchDetector* p = *detector;
+    
+    tPeriodDetector_setHysteresis(&p->_pd, hysteresis);
+}
+
 static inline float calculate_frequency(tPitchDetector* const detector)
 {
     _tPitchDetector* p = *detector;
     
-    if (p->_pd->_period_info[0] != -1)
-        return leaf.sampleRate / p->_pd->_period_info[0];
+    float period = p->_pd->_fundamental.period;
+    if (period > 0.0f)
+        return leaf.sampleRate / period;
     return 0.0f;
 }
 
-static inline void bias(tPitchDetector* const detector, float incoming)
+static inline void bias(tPitchDetector* const detector, _pitch_info incoming)
 {
     _tPitchDetector* p = *detector;
     
-    float current = p->_frequency;
     ++p->_frames_after_shift;
     int shifted = 0;
     
-    float f;
+    _pitch_info result;
     
     //=============================================================================
-    //float f = bias(current, incoming, shift);
+    //_pitch_info result = bias(current, incoming, shift);
     {
-        float error = current / 32.0f; // approx 1/2 semitone
-        float diff = fabsf(current - incoming);
+        float error = p->_current.frequency / 32.0f; // approx 1/2 semitone
+        float diff = fabsf(p->_current.frequency - incoming.frequency);
         int done = 0;
         
         // Try fundamental
         if (diff < error)
         {
-            f = incoming;
+            result = incoming;
             done = 1;
         }
         // Try harmonics and sub-harmonics
-        else if (p->_frames_after_shift > 2)
+        else if (p->_frames_after_shift > 1)
         {
-            if (current > incoming)
+            if (p->_current.frequency > incoming.frequency)
             {
-                float multiple = roundf(current / incoming);
-                if (multiple > 1.0f)
+                int multiple = roundf(p->_current.frequency / incoming.frequency);
+                if (multiple > 1)
                 {
-                    float h = incoming * multiple;
-                    if (fabsf(current - h) < error)
+                    float f = incoming.frequency * multiple;
+                    if (fabsf(p->_current.frequency - f) < error)
                     {
-                        f = h;
+                        result.frequency = f;
+                        result.periodicity = incoming.periodicity;
                         done = 1;
                     }
                 }
@@ -1999,13 +2048,14 @@
             }
             else
             {
-                float multiple = roundf(incoming / current);
-                if (multiple > 1.0f)
+                int multiple = roundf(incoming.frequency / p->_current.frequency);
+                if (multiple > 1)
                 {
-                    float h = incoming / multiple;
-                    if (fabsf(current - h) < error)
+                    float f = incoming.frequency / multiple;
+                    if (fabsf(p->_current.frequency - f) < error)
                     {
-                        f = h;
+                        result.frequency = f;
+                        result.periodicity = incoming.periodicity;
                         done = 1;
                     }
                 }
@@ -2018,13 +2068,13 @@
         // harmonic matches).
         if (!done)
         {
-            if (p->_pd->_period_info[1] > MIN_PERIODICITY)
+            if (p->_pd->_fundamental.periodicity > MIN_PERIODICITY)
             {
                 // Now we have a frequency shift
                 shifted = 1;
-                f = incoming;
+                result = incoming;
             }
-            else f = current;
+            else result = p->_current;
         }
     }
     
@@ -2034,128 +2084,161 @@
     // Note that we only do this check on frequency shifts
     if (shifted)
     {
-        if (p->_pd->_period_info[1] < MAX_DEVIATION)
+        float periodicity = p->_pd->_fundamental.periodicity;
+        if (periodicity >= ONSET_PERIODICITY)
         {
-            // If we don't have enough confidence in the autocorrelation
-            // result, we'll try the zero-crossing edges to extract the
-            // frequency and the one closest to the current frequency wins.
-            int shifted2 = 0;
-            float predicted = tPitchDetector_predictFrequency(detector, 0);
-            if (predicted > 0.0f)
-            {
-                float f2;
-                
-                //=============================================================================
-//              float f2 = bias(current, predicted, shifted2);
-                {
-                    float error = current / 32.0f; // approx 1/2 semitone
-                    float diff = fabsf(current - predicted);
-                    int done = 0;
-                    
-                    // Try fundamental
-                    if (diff < error)
-                    {
-                        f2 = predicted;
-                        done = 1;
-                    }
-                    // Try harmonics and sub-harmonics
-                    else if (p->_frames_after_shift > 2)
-                    {
-                        if (current > predicted)
-                        {
-                            float multiple = roundf(current / predicted);
-                            if (multiple > 1.0f)
-                            {
-                                float h = predicted * multiple;
-                                if (fabsf(current - h) < error)
-                                {
-                                    f2 = h;
-                                    done = 1;
-                                }
-                            }
-                        }
-                        else
-                        {
-                            float multiple = roundf(predicted / current);
-                            if (multiple > 1.0f)
-                            {
-                                float h = predicted / multiple;
-                                if (fabsf(current - h) < error)
-                                {
-                                    f2 = h;
-                                    done = 1;
-                                }
-                            }
-                        }
-                    }
-                    // Don't do anything if the latest autocorrelation is not periodic
-                    // enough. Note that we only do this check on frequency shifts (i.e. at
-                    // this point, we are looking at a potential frequency shift, after
-                    // passing through the code above, checking for fundamental and
-                    // harmonic matches).
-                    if (!done)
-                    {
-                        if (p->_pd->_period_info[1] > MIN_PERIODICITY)
-                        {
-                            // Now we have a frequency shift
-                            shifted2 = 1;
-                            f2 = predicted;
-                        }
-                        else f2 = current;
-                    }
-                }
-                
-                //=============================================================================
-                
-                // If there's no shift, the edges wins
-                if (!shifted2)
-                {
-                    p->_median = median3f(f2, p->_median_b, p->_median_c);
-                    p->_median_c = p->_median_b;
-                    p->_median_b = f2;
-                    p->_frequency = p->_median;
-                }
-                else // else, whichever is closest to the current frequency wins.
-                {
-                    int predicted = fabsf(current - f) >= fabsf(current - f2);
-                    float pf = !predicted ? f : f2;
-                    p->_median = median3f(pf, p->_median_b, p->_median_c);
-                    p->_median_c = p->_median_b;
-                    p->_median_b = pf;
-                    p->_frequency = p->_median;
-                }
-            }
-            else
+            p->_frames_after_shift = 0;
+            p->_current = result;
+        }
+        else if (periodicity < MIN_PERIODICITY)
+        {
+            p->_current.frequency = 0.0f;
+            p->_current.periodicity = 0.0f;
+        }
+    }
+    else
+    {
+        p->_current = result;
+    }
+}
+
+static inline int within_octave(tDualPitchDetector* const detector, float f);
+static inline void compute_predicted_frequency(tDualPitchDetector* const detector);
+
+void    tDualPitchDetector_init (tDualPitchDetector* const detector, float lowestFreq, float highestFreq)
+{
+    tDualPitchDetector_initToPool(detector, lowestFreq, highestFreq, &leaf.mempool);
+}
+
+void    tDualPitchDetector_initToPool   (tDualPitchDetector* const detector, float lowestFreq, float highestFreq, tMempool* const mempool)
+{
+    _tMempool* m = *mempool;
+    _tDualPitchDetector* p = *detector = (_tDualPitchDetector*) mpool_alloc(sizeof(_tDualPitchDetector), m);
+    p->mempool = m;
+    
+    tPitchDetector_initToPool(&p->_pd1, lowestFreq, highestFreq, mempool);
+    tPitchDetector_initToPool(&p->_pd2, lowestFreq, highestFreq, mempool);
+    p->_current.frequency = 0.0f;
+    p->_current.periodicity = 0.0f;
+    p->_mean = lowestFreq + ((highestFreq - lowestFreq) / 2.0f);
+    p->_predicted_frequency = 0.0f;
+    p->_first = 1;
+}
+
+void    tDualPitchDetector_free (tDualPitchDetector* const detector)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    tPitchDetector_free(&p->_pd1);
+    tPitchDetector_free(&p->_pd2);
+    
+    mpool_free((char*) p, p->mempool);
+}
+
+int     tDualPitchDetector_tick    (tDualPitchDetector* const detector, float sample)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    int pd1_ready = tPitchDetector_tick(&p->_pd1, sample);
+    int pd2_ready = tPitchDetector_tick(&p->_pd2, -sample);
+    
+    if (pd1_ready || pd2_ready)
+    {
+        int pd1_indeterminate = tPitchDetector_indeterminate(&p->_pd1);
+        int pd2_indeterminate = tPitchDetector_indeterminate(&p->_pd2);
+        if (!pd1_indeterminate && !pd2_indeterminate)
+        {
+            _pitch_info _i1 = p->_pd1->_current;
+            _pitch_info _i2 = p->_pd2->_current;
+            
+            float pd1_diff = fabsf(_i1.frequency - p->_mean);
+            float pd2_diff = fabsf(_i2.frequency - p->_mean);
+            _pitch_info i = (pd1_diff < pd2_diff) ? _i1 : _i2;
+            
+            if (p->_first)
+            {
+                p->_current = i;
+                p->_mean = p->_current.frequency;
+                p->_first = 0;
+                p->_predicted_frequency = 0.0f;
+            }
+            else if (within_octave(detector, i.frequency))
+            {
+                p->_current = i;
+                p->_mean = p->_current.frequency;//(0.222222f * p->_current.frequency) + (0.888888f * p->_mean);
+                p->_predicted_frequency = 0.0f;
+            }
+        }
+        
+        if (pd1_indeterminate && pd2_indeterminate)
+        {
+            compute_predicted_frequency(detector);
+            p->_current.frequency = 0.0f;
+            p->_current.periodicity = 0.0f;
+        }
+    }
+    
+    return pd1_ready || pd2_ready;
+}
+
+float   tDualPitchDetector_getFrequency    (tDualPitchDetector* const detector)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    return p->_current.frequency;
+}
+
+float   tDualPitchDetector_getPeriodicity  (tDualPitchDetector* const detector)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    return p->_current.periodicity;
+}
+
+float   tDualPitchDetector_predictFrequency (tDualPitchDetector* const detector)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    if (p->_predicted_frequency == 0.0f)
+        compute_predicted_frequency(detector);
+    return p->_predicted_frequency;
+}
+
+void    tDualPitchDetector_setHysteresis    (tDualPitchDetector* const detector, float hysteresis)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    tPitchDetector_setHysteresis(&p->_pd1, hysteresis);
+    tPitchDetector_setHysteresis(&p->_pd2, hysteresis);
+}
+
+static inline int within_octave(tDualPitchDetector* const detector, float f)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    return (f > p->_mean) ? (f < (p->_mean * 2.0f)) : (f > (p->_mean * 0.5f));
+}
+
+static inline void compute_predicted_frequency(tDualPitchDetector* const detector)
+{
+    _tDualPitchDetector* p = *detector;
+    
+    float f1 = tPitchDetector_predictFrequency(&p->_pd1);
+    if (f1 > 0.0f)
+    {
+        float f2 = tPitchDetector_predictFrequency(&p->_pd2);
+        if (f2 > 0.0f)
+        {
+            float error = f1 * 0.1f;
+            if (fabsf(f1 - f2) < error)
             {
-                p->_median = median3f(f, p->_median_b, p->_median_c);
-                p->_median_c = p->_median_b;
-                p->_median_b = f;
-                p->_frequency = p->_median;
+                if (p->_first || within_octave(detector, f1))
+                {
+                    p->_predicted_frequency = f1;
+                    return;
+                }
             }
-            
         }
-        else
-        {
-            // Now we have a frequency shift. Get the median of 3 (incoming
-            // frequency and last two frequency shifts) to eliminate abrupt
-            // changes. This will minimize potentially unwanted shifts.
-            // See https://en.wikipedia.org/wiki/Median_filter
-
-            p->_median = median3f(incoming, p->_median_b, p->_median_c);
-            p->_median_c = p->_median_b;
-            p->_median_b = incoming;
-            
-            if (p->_median == incoming)
-                p->_frames_after_shift = 0;
-            
-            p->_frequency = f;
-        }
     }
-    else
-    {
-        p->_median = median3f(f, p->_median_b, p->_median_c);
-        p->_median_c = p->_median_b;
-        p->_median_b = f;
-        p->_frequency = p->_median;
-    }
+    p->_predicted_frequency = 0.0f;
 }