oboe: add PolyphaseSincResampler

refactor resampler API
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 36802a9..5303f26 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,10 +22,13 @@
     src/fifo/FifoControllerIndirect.cpp
     src/flowgraph/AudioProcessorBase.cpp
     src/flowgraph/ClipToRange.cpp
+    src/flowgraph/ContinuousResampler.cpp
+    src/flowgraph/IntegerRatio.cpp
     src/flowgraph/LinearResampler.cpp
     src/flowgraph/ManyToMultiConverter.cpp
     src/flowgraph/MonoToMultiConverter.cpp
     src/flowgraph/MultiChannelResampler.cpp
+    src/flowgraph/PolyphaseSincResampler.cpp
     src/flowgraph/RampLinear.cpp
     src/flowgraph/SampleRateConverter.cpp
     src/flowgraph/SincResampler.cpp
diff --git a/apps/OboeTester/app/CMakeLists.txt b/apps/OboeTester/app/CMakeLists.txt
index a3accd9..38616c4 100644
--- a/apps/OboeTester/app/CMakeLists.txt
+++ b/apps/OboeTester/app/CMakeLists.txt
@@ -26,6 +26,7 @@
     ${OBOE_DIR}/src
 )
 
+
 ### END OBOE INCLUDE SECTION ###
 
 # link to oboe
diff --git a/src/common/DataConversionFlowGraph.cpp b/src/common/DataConversionFlowGraph.cpp
index 31ee8bd..ac12ef4 100644
--- a/src/common/DataConversionFlowGraph.cpp
+++ b/src/common/DataConversionFlowGraph.cpp
@@ -30,6 +30,7 @@
 #include <flowgraph/SourceFloat.h>
 #include <flowgraph/SourceI16.h>
 #include <flowgraph/SourceI24.h>
+#include <flowgraph/SampleRateConverter.h>
 
 using namespace oboe;
 using namespace flowgraph;
@@ -112,11 +113,12 @@
     if (sourceSampleRate != sinkSampleRate) {
 
         mResampler.reset(MultiChannelResampler::make(sourceChannelCount,
+                                                     sourceSampleRate,
+                                                     sinkSampleRate,
                                                      convertOboeSRQualityToMCR(
                                                              stream->getSampleRateConversionType())));
         mRateConverter = std::make_unique<SampleRateConverter>(sourceChannelCount,
                                                                *mResampler.get());
-        mRateConverter->setPhaseIncrement((double) sourceSampleRate / sinkSampleRate);
         lastOutput->connect(&mRateConverter->input);
         lastOutput = &mRateConverter->output;
     }
diff --git a/src/flowgraph/ContinuousResampler.cpp b/src/flowgraph/ContinuousResampler.cpp
new file mode 100644
index 0000000..80e140d
--- /dev/null
+++ b/src/flowgraph/ContinuousResampler.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ContinuousResampler.h"
diff --git a/src/flowgraph/ContinuousResampler.h b/src/flowgraph/ContinuousResampler.h
new file mode 100644
index 0000000..c1f97a8
--- /dev/null
+++ b/src/flowgraph/ContinuousResampler.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_CONTINUOUS_RESAMPLER_H
+#define FLOWGRAPH_CONTINUOUS_RESAMPLER_H
+
+#include <sys/types.h>
+#include <unistd.h>
+#include "MultiChannelResampler.h"
+
+namespace flowgraph {
+
+/*
+ * Resampler that uses a double precision phase internally.
+ */
+class ContinuousResampler : public MultiChannelResampler {
+public:
+    explicit ContinuousResampler(int32_t channelCount,
+                                 int32_t numTaps,
+                                 int32_t inputRate,
+                                 int32_t outputRate)
+            : MultiChannelResampler(channelCount, numTaps, inputRate, outputRate) {
+        mPhaseIncrement = (double) inputRate / (double) outputRate;
+    }
+
+    virtual ~ContinuousResampler() = default;
+
+    bool isWriteReady() const override {
+        return mPhase >= 1.0;
+    }
+
+    virtual void advanceWrite() override {
+        mPhase -= 1.0;
+    }
+
+    bool isReadReady() const override {
+        return mPhase < 1.0;
+    }
+
+    virtual void advanceRead() override {
+        mPhase += mPhaseIncrement;
+    }
+
+    float getPhase() {
+        return (float) mPhase;
+    }
+
+private:
+    double mPhase = 1.0;
+    double mPhaseIncrement = 1.0;
+};
+
+}
+#endif //FLOWGRAPH_CONTINUOUS_RESAMPLER_H
diff --git a/src/flowgraph/IntegerRatio.cpp b/src/flowgraph/IntegerRatio.cpp
new file mode 100644
index 0000000..983c006
--- /dev/null
+++ b/src/flowgraph/IntegerRatio.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "IntegerRatio.h"
+
+using namespace flowgraph;
+
+std::vector<int>  IntegerRatio::kPrimes{
+        2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
+        43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
+        101, 103, 107, 109, 113, 127, 131, 137, 139, 149,
+        151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199};
+
+void IntegerRatio::reduce() {
+    int32_t factoredTop = mNumerator;
+    int32_t factoredBottom = mDenominator;
+    bool downToOne = false;
+    for (int prime : kPrimes) {
+        if (mNumerator < prime
+            || mDenominator < prime
+            || downToOne)
+            break;
+        int topCount = 0;
+        while (true) {
+            int top = factoredTop / prime;
+            if (factoredTop == top * prime) {
+                topCount++;
+                factoredTop = top;
+                downToOne |= top == 1;
+            } else {
+                break;
+            }
+        }
+        int bottomCount = 0;
+        while (true) {
+            int bottom = factoredBottom / prime;
+            if (factoredBottom == bottom * prime) {
+                bottomCount++;
+                factoredBottom = bottom;
+                downToOne |= bottom == 1;
+            } else {
+                break;
+            }
+        }
+
+        int commonCount = std::min(topCount, bottomCount);
+        while (commonCount--) {
+            mNumerator /= prime;
+            mDenominator /= prime;
+        }
+    }
+}
diff --git a/src/flowgraph/IntegerRatio.h b/src/flowgraph/IntegerRatio.h
new file mode 100644
index 0000000..5fb8c1b
--- /dev/null
+++ b/src/flowgraph/IntegerRatio.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_INTEGER_RATIO_H
+#define FLOWGRAPH_INTEGER_RATIO_H
+
+#include <sys/types.h>
+#include <vector>
+
+namespace flowgraph {
+
+class IntegerRatio {
+public:
+    IntegerRatio(int32_t numerator, int32_t denominator)
+            : mNumerator(numerator), mDenominator(denominator) {}
+
+    /** Reduce by removing common prime factors.
+     */
+    void reduce();
+
+    int32_t getNumerator() {
+        return mNumerator;
+    }
+
+    int32_t getDenominator() {
+        return mDenominator;
+    }
+
+private:
+    int32_t mNumerator;
+    int32_t mDenominator;
+    static std::vector<int> kPrimes;
+};
+
+}
+
+#endif //FLOWGRAPH_INTEGER_RATIO_H
diff --git a/src/flowgraph/LinearResampler.cpp b/src/flowgraph/LinearResampler.cpp
index 27f6702..2dc1d9b 100644
--- a/src/flowgraph/LinearResampler.cpp
+++ b/src/flowgraph/LinearResampler.cpp
@@ -18,8 +18,10 @@
 
 using namespace flowgraph;
 
-LinearResampler::LinearResampler(int32_t channelCount)
-        : MultiChannelResampler(channelCount) {
+LinearResampler::LinearResampler(int32_t channelCount,
+                                 int32_t inputRate,
+                                 int32_t outputRate)
+        : ContinuousResampler(channelCount, 2 /* numTaps */, inputRate,outputRate) {
         mPreviousFrame = std::make_unique<float[]>(channelCount);
         mCurrentFrame = std::make_unique<float[]>(channelCount);
 }
@@ -29,12 +31,13 @@
     memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount());
 }
 
-void LinearResampler::readFrame(float *frame, float mPhase) {
+void LinearResampler::readFrame(float *frame) {
     float *previous = mPreviousFrame.get();
     float *current = mCurrentFrame.get();
+    float phase = (float) getPhase();
     for (int channel = 0; channel < getChannelCount(); channel++) {
         float f0 = *previous++;
         float f1 = *current++;
-        *frame++ = f0 + (mPhase * (f1 - f0));
+        *frame++ = f0 + (phase * (f1 - f0));
     }
 }
diff --git a/src/flowgraph/LinearResampler.h b/src/flowgraph/LinearResampler.h
index e91048e..b294df1 100644
--- a/src/flowgraph/LinearResampler.h
+++ b/src/flowgraph/LinearResampler.h
@@ -20,17 +20,19 @@
 #include <memory>
 #include <sys/types.h>
 #include <unistd.h>
-#include "MultiChannelResampler.h"
+#include "ContinuousResampler.h"
 
 namespace flowgraph {
 
-class LinearResampler : public MultiChannelResampler{
+class LinearResampler : public ContinuousResampler {
 public:
-    explicit LinearResampler(int32_t channelCount);
+    LinearResampler(int32_t channelCount,
+                             int32_t inputRate,
+                             int32_t outputRate);
 
     void writeFrame(const float *frame) override;
 
-    void readFrame(float *frame, float mPhase) override;
+    void readFrame(float *frame) override;
 
 private:
     std::unique_ptr<float[]> mPreviousFrame;
diff --git a/src/flowgraph/MultiChannelResampler.cpp b/src/flowgraph/MultiChannelResampler.cpp
index 62af660..a4b15ab 100644
--- a/src/flowgraph/MultiChannelResampler.cpp
+++ b/src/flowgraph/MultiChannelResampler.cpp
@@ -14,25 +14,60 @@
  * limitations under the License.
  */
 
+#include <math.h>
+
 #include "MultiChannelResampler.h"
 #include "LinearResampler.h"
 #include "SincResampler.h"
+#include "PolyphaseSincResampler.h"
 #include "SincResamplerStereo.h"
 
 using namespace flowgraph;
 
-MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount, Quality quality) {
+MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
+                                                   int32_t inputRate,
+                                                   int32_t outputRate,
+                                                   Quality quality) {
     switch (quality) {
         case Quality::Low:
         case Quality::Medium: // TODO polynomial
-            return new LinearResampler(channelCount);
+            return new LinearResampler(channelCount, inputRate, outputRate);
         default:
         case Quality::High:
+            return new PolyphaseSincResampler(channelCount, inputRate, outputRate); // TODO
         case Quality::Best:
             if (channelCount == 2) {
-                return new SincResamplerStereo(); // TODO pass spread
+                return new SincResamplerStereo( inputRate, outputRate); // TODO pass spread
             } else {
-                return new SincResampler(channelCount); // TODO pass spread
+                return new SincResampler(channelCount,  inputRate, outputRate); // TODO pass spread
             }
     }
 }
+
+
+void MultiChannelResampler::writeFrame(const float *frame) {
+    // Advance cursor before write so that cursor points to last written frame in read.
+    if (++mCursor >= getNumTaps()) {
+        mCursor = 0;
+    }
+    int xIndex = mCursor * getChannelCount();
+    int offset = getNumTaps() * getChannelCount();
+    float *dest = &mX[xIndex];
+    for (int channel = 0; channel < getChannelCount(); channel++) {
+        // Write twice so we avoid having to wrap when running the FIR.
+        dest[channel] = dest[channel + offset] = frame[channel];
+    }
+}
+
+
+float MultiChannelResampler::calculateWindowedSinc(float phase, int spread) {
+    const float realPhase = phase - spread;
+    if (abs(realPhase) < 0.00000001) return 1.0f; // avoid divide by zero
+    // Hamming window TODO try Kaiser window
+    const float alpha = 0.54f;
+    const float windowPhase = realPhase * M_PI / spread;
+    const float window = (float) (alpha + ((1.0 - alpha) * cosf(windowPhase)));
+    const float sincPhase = realPhase * M_PI;
+    const float sinc = sinf(sincPhase) / sincPhase;
+    return window * sinc;
+}
diff --git a/src/flowgraph/MultiChannelResampler.h b/src/flowgraph/MultiChannelResampler.h
index ec4671b..2286eba 100644
--- a/src/flowgraph/MultiChannelResampler.h
+++ b/src/flowgraph/MultiChannelResampler.h
@@ -18,35 +18,61 @@
 #define FLOWGRAPH_MULTICHANNEL_RESAMPLER_H
 
 #include <memory>
+#include <vector>
 #include <sys/types.h>
 #include <unistd.h>
 
 namespace flowgraph {
 
 class MultiChannelResampler {
+
 public:
-    explicit MultiChannelResampler(int32_t channelCount)
-        : mChannelCount(channelCount) {}
+
+    MultiChannelResampler(int32_t channelCount,
+            int32_t numTaps,
+            int32_t inputRate,
+            int32_t outputRate)
+        : mChannelCount(channelCount)
+        , mNumTaps(numTaps)
+        , mX(channelCount * getNumTaps() * 2)
+        , mSingleFrame(channelCount)
+            {}
 
     virtual ~MultiChannelResampler() = default;
 
+    virtual bool isReadReady() const = 0;
+    virtual bool isWriteReady() const = 0;
+
+    virtual void advanceWrite() = 0;
+    virtual void advanceRead() = 0;
+
     /**
      * Write a frame containing N samples.
-     * @param frame
+     * @param frame pointer to the first sample in a frame
      */
-    virtual void writeFrame(const float *frame) = 0;
+    virtual void writeFrame(const float *frame);
 
     /**
      * Read a frame containing N samples using interpolation.
-     * @param frame
+     * @param frame pointer to the first sample in a frame
      * @param phase phase between 0.0 and 1.0 for interpolation
      */
-    virtual void readFrame(float *frame, float phase) = 0;
+    virtual void readFrame(float *frame) = 0;
+
+    int getNumTaps() const {
+        return mNumTaps;
+    }
 
     int getChannelCount() const {
         return mChannelCount;
     }
 
+    /**
+     * @param phase between 0.0 and  2*spread
+     * @return windowedSinc
+     */
+    static float calculateWindowedSinc(float phase, int spread);
+
     enum class Quality : int32_t {
         Low,
         Medium,
@@ -54,10 +80,23 @@
         Best,
     };
 
-    static MultiChannelResampler *make(int32_t channelCount, Quality quality);
+    static MultiChannelResampler *make(int32_t channelCount,
+            int32_t inputRate,
+            int32_t outputRate,
+            Quality quality);
 
 private:
-    const int mChannelCount;
+
+    const int          mChannelCount;
+    const int          mNumTaps;
+
+protected:
+
+    int                mCursor = 0;
+    std::vector<float> mX;
+    std::vector<float> mSingleFrame;
+
+
 };
 
 }
diff --git a/src/flowgraph/PolyphaseSincResampler.cpp b/src/flowgraph/PolyphaseSincResampler.cpp
new file mode 100644
index 0000000..9ed3212
--- /dev/null
+++ b/src/flowgraph/PolyphaseSincResampler.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IntegerRatio.h"
+#include "PolyphaseSincResampler.h"
+
+using namespace flowgraph;
+
+PolyphaseSincResampler::PolyphaseSincResampler(int32_t channelCount,
+                             int32_t inputRate,
+                             int32_t outputRate)
+        : MultiChannelResampler(channelCount, kNumTaps, inputRate, outputRate)
+        {
+    generateCoefficients(inputRate, outputRate);
+}
+
+void PolyphaseSincResampler::generateCoefficients(int32_t inputRate, int32_t outputRate) {
+    IntegerRatio ratio(inputRate, outputRate);
+    ratio.reduce();
+    mNumerator = ratio.getNumerator();
+    mDenominator = ratio.getDenominator();
+    mIntegerPhase = mDenominator;
+    mCoefficients.resize(getNumTaps() * ratio.getDenominator());
+    int cursor = 0;
+    double phase = 0.0;
+    double phaseIncrement = (double) inputRate / (double) outputRate;
+    for (int i = 0; i < ratio.getDenominator(); i++) {
+        float tapPhase = phase;
+        for (int tap = 0; tap < getNumTaps(); tap++) {
+            mCoefficients.at(cursor++) = calculateWindowedSinc(tapPhase, kSpread);
+            tapPhase += 1.0;
+        }
+        phase += phaseIncrement;
+        while (phase >= 1.0) {
+            phase -= 1.0;
+        }
+    }
+}
+void PolyphaseSincResampler::readFrame(float *frame) {
+    // Clear accumulator for mix.
+    for (int channel = 0; channel < getChannelCount(); channel++) {
+        mSingleFrame[channel] = 0.0;
+    }
+
+    float *coefficients = &mCoefficients[mCoefficientCursor];
+    // Multiply input times windowed sinc function.
+    int xIndex = (mCursor + kNumTaps) * getChannelCount();
+    for (int i = 0; i < kNumTaps; i++) {
+        float coefficient = *coefficients++;
+        float *xFrame = &mX[xIndex];
+        for (int channel = 0; channel < getChannelCount(); channel++) {
+            mSingleFrame[channel] += coefficient * xFrame[channel];
+        }
+        xIndex -= getChannelCount();
+    }
+
+    mCoefficientCursor += kNumTaps;
+    if (mCoefficientCursor >= mCoefficients.size()) {
+        mCoefficientCursor = 0;
+    }
+
+    // Copy accumulator to output.
+    for (int channel = 0; channel < getChannelCount(); channel++) {
+        frame[channel] = mSingleFrame[channel];
+    }
+}
diff --git a/src/flowgraph/PolyphaseSincResampler.h b/src/flowgraph/PolyphaseSincResampler.h
new file mode 100644
index 0000000..e64966f
--- /dev/null
+++ b/src/flowgraph/PolyphaseSincResampler.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
+#define FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
+
+
+#include <memory>
+#include <vector>
+#include <sys/types.h>
+#include <unistd.h>
+#include "MultiChannelResampler.h"
+
+namespace flowgraph {
+
+class PolyphaseSincResampler : public MultiChannelResampler {
+public:
+    PolyphaseSincResampler(int32_t channelCount, int32_t inputRate, int32_t outputRate);
+
+    virtual ~PolyphaseSincResampler() = default;
+
+    void readFrame(float *frame) override;
+
+    int getSpread() const {
+        return kSpread;
+    }
+
+    bool isWriteReady() const override {
+        return mIntegerPhase >= mDenominator;
+    }
+
+    virtual void advanceWrite() override {
+        mIntegerPhase -= mDenominator;
+    }
+
+    bool isReadReady() const override {
+        return mIntegerPhase < mDenominator;
+    }
+
+    virtual void advanceRead() override {
+        mIntegerPhase += mNumerator;
+    }
+
+protected:
+
+    void generateCoefficients(int32_t inputRate, int32_t outputRate);
+    
+    // Number of zero crossings on one side of central lobe.
+    // Higher numbers provide higher quality but use more CPU.
+    // 2 is the minimum one should use.
+    static constexpr int   kSpread = 10;
+    static constexpr int   kNumTaps = kSpread * 2; // TODO should be odd, not even
+
+    std::vector<float>     mCoefficients;
+    int32_t                mCoefficientCursor = 0;
+    int32_t                mIntegerPhase = 0;
+    int32_t                mNumerator = 0;
+    int32_t                mDenominator = 0;
+
+};
+
+}
+
+#endif //FLOWGRAPH_POLYPHASE_SINC_RESAMPLER_H
diff --git a/src/flowgraph/SampleRateConverter.cpp b/src/flowgraph/SampleRateConverter.cpp
index 84c9bda..55d8310 100644
--- a/src/flowgraph/SampleRateConverter.cpp
+++ b/src/flowgraph/SampleRateConverter.cpp
@@ -45,24 +45,21 @@
     int framesLeft = numFrames;
     while (framesLeft > 0) {
         // Gather input samples as needed.
-        while(mPhase >= 1.0) {
-            if (!isInputAvailable()) break;
-            mPhase -= 1.0;
-            const float *frame = getNextInputFrame();
-            mResampler.writeFrame(frame);
-        }
-
-        // If phase >= 1.0 then we are waiting for input data.
-        if (mPhase < 1.0) {
+        if(mResampler.isWriteReady()) {
+            if (isInputAvailable()) {
+                const float *frame = getNextInputFrame();
+                mResampler.writeFrame(frame);
+                mResampler.advanceWrite();
+            } else {
+                break;
+            }
+        } else {
             // Output frame is interpolated from input samples based on phase.
-            mResampler.readFrame(outputBuffer, mPhase);
-            mPhase += mPhaseIncrement;
+            mResampler.readFrame(outputBuffer);
+            mResampler.advanceRead();
             outputBuffer += channelCount;
             framesLeft--;
-        } else {
-            break;
         }
     }
     return numFrames - framesLeft;
 }
-
diff --git a/src/flowgraph/SampleRateConverter.h b/src/flowgraph/SampleRateConverter.h
index 046397c..9e2d900 100644
--- a/src/flowgraph/SampleRateConverter.h
+++ b/src/flowgraph/SampleRateConverter.h
@@ -35,14 +35,6 @@
 
     int32_t onProcess(int32_t numFrames) override;
 
-    double getPhaseIncrement() {
-        return mPhaseIncrement;
-    }
-
-    void setPhaseIncrement(double phaseIncrement) {
-        mPhaseIncrement = phaseIncrement;
-    }
-
     const char *getName() override {
         return "SampleRateConverter";
     }
@@ -56,8 +48,6 @@
 
     MultiChannelResampler &mResampler;
 
-    double  mPhase = 1.0;
-    double  mPhaseIncrement = 1.0;
     int32_t mInputCursor = 0;
     int32_t mInputValid = 0;
     int64_t mInputFramePosition = 0; // monotonic counter of input frames used for pullData
diff --git a/src/flowgraph/SampleRateConverterVariable.cpp b/src/flowgraph/SampleRateConverterVariable.cpp
new file mode 100644
index 0000000..4a3f103
--- /dev/null
+++ b/src/flowgraph/SampleRateConverterVariable.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SampleRateConverterVariable.h"
+
+using namespace flowgraph;
+
+SampleRateConverterVariable::SampleRateConverterVariable(int32_t channelCount, MultiChannelResampler &resampler)
+        : SampleRateConverterVariable(channelCount, resampler) {
+}
+
+int32_t SampleRateConverterVariable::onProcess(int32_t numFrames) {
+    float *outputBuffer = output.getBuffer();
+    int32_t channelCount = output.getSamplesPerFrame();
+    int framesLeft = numFrames;
+    while (framesLeft > 0) {
+        // Gather input samples as needed.
+        if(mResampler.isWriteReady()) {
+            if (!isInputAvailable()) break;
+            const float *frame = getNextInputFrame();
+            mResampler.writeFrame(frame);
+        }
+
+        // If phase >= 1.0 then we are waiting for input data.
+        if (mResampler.isReadReady()) {
+            // Output frame is interpolated from input samples based on phase.
+            mResampler.readFrame(outputBuffer);
+            outputBuffer += channelCount;
+            framesLeft--;
+        } else {
+            break;
+        }
+    }
+    return numFrames - framesLeft;
+}
diff --git a/src/flowgraph/SampleRateConverterVariable.h b/src/flowgraph/SampleRateConverterVariable.h
new file mode 100644
index 0000000..a215ac1
--- /dev/null
+++ b/src/flowgraph/SampleRateConverterVariable.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
+#define OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "AudioProcessorBase.h"
+#include "MultiChannelResampler.h"
+#include "LinearResampler.h"
+#include "SincResampler.h"
+
+namespace flowgraph {
+
+class SampleRateConverterVariable : public SampleRateConverterVariable {
+public:
+    explicit SampleRateConverterVariable(int32_t channelCount, MultiChannelResampler &mResampler);
+
+    double getPhaseIncrement() {
+        return mPhaseIncrement;
+    }
+
+    void setPhaseIncrement(double phaseIncrement) {
+        mPhaseIncrement = phaseIncrement;
+    }
+
+    int32_t onProcess(int32_t numFrames) override;
+
+private:
+    double  mPhase = 1.0;
+    double  mPhaseIncrement = 1.0;
+};
+
+} /* namespace flowgraph */
+
+#endif //OBOE_SAMPLE_RATE_CONVERTER_VARIABLE_H
diff --git a/src/flowgraph/SincResampler.cpp b/src/flowgraph/SincResampler.cpp
index f833c32..baaba35 100644
--- a/src/flowgraph/SincResampler.cpp
+++ b/src/flowgraph/SincResampler.cpp
@@ -19,33 +19,21 @@
 
 using namespace flowgraph;
 
-SincResampler::SincResampler(int32_t channelCount)
-        : MultiChannelResampler(channelCount)
-        , mX(channelCount * kNumTaps * 2)
-        , mSingleFrame(channelCount)
+SincResampler::SincResampler(int32_t channelCount,
+                             int32_t inputRate,
+                             int32_t outputRate)
+        : ContinuousResampler(channelCount, kNumTaps, inputRate, outputRate)
         , mWindowedSinc(kNumPoints + kNumGuardPoints){
     generateLookupTable();
 }
 
-void SincResampler::writeFrame(const float *frame) {
-    // Advance cursor before write so that cursor points to last written frame in read.
-    if (++mCursor >= kNumTaps) {
-        mCursor = 0;
-    }
-    int xIndex = mCursor * getChannelCount();
-    int offset = kNumTaps * getChannelCount();
-    float *dest = &mX[xIndex];
-    for (int channel = 0; channel < getChannelCount(); channel++) {
-        // Write twice so we avoid having to wrap when running the FIR.
-        dest[channel] = dest[channel + offset] = frame[channel];
-    }
-}
 
-void SincResampler::readFrame(float *frame, float phase) {
+void SincResampler::readFrame(float *frame) {
     // Clear accumulator for mix.
     for (int channel = 0; channel < getChannelCount(); channel++) {
         mSingleFrame[channel] = 0.0;
     }
+    float phase =  getPhase();
     // Multiply input times windowed sinc function.
     int xIndex = (mCursor + kNumTaps) * getChannelCount();
     for (int i = 0; i < kNumTaps; i++) {
@@ -89,23 +77,11 @@
     return low + (fraction * (high - low));
 }
 
-float SincResampler::calculateWindowedSinc(float phase) {
-    const float realPhase = phase - kSpread;
-    if (abs(realPhase) < 0.00000001) return 1.0f; // avoid divide by zero
-    // Hamming window TODO try Kaiser window
-    const float alpha = 0.54f;
-    const float windowPhase = realPhase * M_PI * kSpreadInverse;
-    const float window = (float) (alpha + ((1.0 - alpha) * cosf(windowPhase)));
-    const float sincPhase = realPhase * M_PI;
-    const float sinc = sinf(sincPhase) / sincPhase;
-    return window * sinc;
-}
-
 void SincResampler::generateLookupTable() {
     // TODO store only half of function and use symmetry
     // By iterating over the table size we also set the guard point.
     for (int i = 0; i < mWindowedSinc.size(); i++) {
         float phase = (i * 2.0 * kSpread) / kNumPoints;
-        mWindowedSinc[i] = calculateWindowedSinc(phase);
+        mWindowedSinc[i] = calculateWindowedSinc(phase, kSpread);
     }
 }
diff --git a/src/flowgraph/SincResampler.h b/src/flowgraph/SincResampler.h
index 20eea51..a887a04 100644
--- a/src/flowgraph/SincResampler.h
+++ b/src/flowgraph/SincResampler.h
@@ -18,22 +18,21 @@
 #define OBOE_SINC_RESAMPLER_H
 
 #include <memory>
-#include <vector>
 #include <sys/types.h>
 #include <unistd.h>
-#include "MultiChannelResampler.h"
+#include "ContinuousResampler.h"
 
 namespace flowgraph {
 
-class SincResampler : public MultiChannelResampler{
+class SincResampler : public ContinuousResampler {
 public:
-    explicit SincResampler(int32_t channelCount);
+    SincResampler(int32_t channelCount,
+                           int32_t inputRate,
+                           int32_t outputRate);
 
     virtual ~SincResampler() = default;
 
-    void writeFrame(const float *frame) override;
-
-    void readFrame(float *frame, float mPhase) override;
+    void readFrame(float *frame) override;
 
     int getSpread() const {
         return kSpread;
@@ -45,26 +44,18 @@
      */
     float interpolateWindowedSinc(float phase);
 
-    /**
-     * @param phase between 0.0 and  2*kSpread
-     * @return windowedSinc
-     */
-    float calculateWindowedSinc(float phase);
-
 protected:
+
     // Number of zero crossings on one side of central lobe.
     // Higher numbers provide higher quality but use more CPU.
     // 2 is the minimum one should use.
     static constexpr int   kSpread = 10;
-    static constexpr int   kNumTaps = kSpread * 2;
-    static constexpr float kSpreadInverse = 1.0 / kSpread;
+    static constexpr int   kNumTaps = kSpread * 2; // TODO should be odd, not even
 
-    std::vector<float> mX;
-    std::vector<float> mSingleFrame;
     std::vector<float> mWindowedSinc;
-    int                mCursor = 0;
 
 private:
+
     void generateLookupTable();
 
     // Size of the lookup table.
diff --git a/src/flowgraph/SincResamplerStereo.cpp b/src/flowgraph/SincResamplerStereo.cpp
index 50c028e..0e5568f 100644
--- a/src/flowgraph/SincResamplerStereo.cpp
+++ b/src/flowgraph/SincResamplerStereo.cpp
@@ -32,9 +32,10 @@
     }
 }
 
-void SincResamplerStereo::readFrame(float *frame, float phase) {
+void SincResamplerStereo::readFrame(float *frame) {
     float left = 0.0;
     float right = 0.0;
+    float phase =  getPhase();
     // Multiply input times windowed sinc function.
     int xIndex = (mCursor + kNumTaps) * STEREO;
     for (int i = 0; i < kNumTaps; i++) {
diff --git a/src/flowgraph/SincResamplerStereo.h b/src/flowgraph/SincResamplerStereo.h
index 06e8e13..1629858 100644
--- a/src/flowgraph/SincResamplerStereo.h
+++ b/src/flowgraph/SincResamplerStereo.h
@@ -25,13 +25,16 @@
 
 class SincResamplerStereo : public SincResampler {
 public:
-    explicit SincResamplerStereo() : SincResampler(2) {}
+    SincResamplerStereo(
+            int32_t inputRate,
+            int32_t outputRate)
+            : SincResampler(2, inputRate, outputRate) {}
 
     virtual ~SincResamplerStereo() = default;
 
     void writeFrame(const float *frame) override;
 
-    void readFrame(float *frame, float mPhase) override;
+    void readFrame(float *frame) override;
 };
 
 }