oboe: fix sample rate converter

Change pullData indexing.
Add pullReset() to reset framePositions.
Add simple test.
diff --git a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
index 6a41c70..e89f914 100644
--- a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
+++ b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
@@ -22,8 +22,6 @@
 
 using namespace flowgraph;
 
-int64_t AudioStreamGateway::mFramePosition = 0;
-
 oboe::DataCallbackResult AudioStreamGateway::onAudioReady(
         oboe::AudioStream *audioStream,
         void *audioData,
diff --git a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
index 2192094..3c147bc 100644
--- a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
+++ b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
@@ -36,6 +36,7 @@
 
     void setAudioSink(std::shared_ptr<flowgraph::AudioSink>  sink) {
         mAudioSink = sink;
+        mFramePosition = sink->getLastFramePosition();
     }
 
     /**
@@ -49,10 +50,7 @@
     int getScheduler();
 
 private:
-    // Use a static position so that it will monotonically increase.
-    // Note only one gateway can be used.
-    // TODO remove single gateway limitation. Add Graph class.
-    static int64_t mFramePosition;
+    int64_t  mFramePosition = 0;
     bool     mSchedulerChecked = false;
     int      mScheduler;
     std::shared_ptr<flowgraph::AudioSink>  mAudioSink;
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
index baf46d8..38d40a8 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
@@ -347,6 +347,10 @@
     manyToMulti->output.connect(&(mSinkFloat.get()->input));
     manyToMulti->output.connect(&(mSinkI16.get()->input));
 
+    // Clear framePosition in sine oscillators.
+    mSinkFloat->pullReset();
+    mSinkI16->pullReset();
+
     configureStreamGateway();
 }
 
diff --git a/apps/OboeTester/app/src/main/res/values/colors.xml b/apps/OboeTester/app/src/main/res/values/colors.xml
index ea15065..fb81fc2 100644
--- a/apps/OboeTester/app/src/main/res/values/colors.xml
+++ b/apps/OboeTester/app/src/main/res/values/colors.xml
@@ -17,6 +17,6 @@
 <resources>
     <color name="waveform_line">#ff101080</color>
     <color name="waveform_background">#ffc0f0c0</color>
-    <color name="version_draft">#ffffe0c0</color>
+    <color name="version_draft">#ffffb0b0</color>
     <color name="version_release">#fff0f0f0</color>
 </resources>
diff --git a/src/common/OboeFlowGraph.cpp b/src/common/OboeFlowGraph.cpp
index 2c3ee8a..3c13050 100644
--- a/src/common/OboeFlowGraph.cpp
+++ b/src/common/OboeFlowGraph.cpp
@@ -34,7 +34,6 @@
 using namespace flowgraph;
 
 void OboeFlowGraph::setSource(const void *buffer, int32_t numFrames) {
-    LOGE("OboeFlowGraph::setSource(,%d) ===================", numFrames);
     mSource->setData(buffer, numFrames);
 }
 
@@ -117,6 +116,8 @@
     }
     lastOutput->connect(&mSink->input);
 
+    mFramePosition = 0;
+
     return Result::OK;
 }
 
diff --git a/src/common/OboeFlowGraph.h b/src/common/OboeFlowGraph.h
index 7323353..7dbe497 100644
--- a/src/common/OboeFlowGraph.h
+++ b/src/common/OboeFlowGraph.h
@@ -33,7 +33,6 @@
 class AudioSourceCaller;
 
 class OboeFlowGraph {
-
 public:
 
     void setSource(const void *buffer, int32_t numFrames);
diff --git a/src/common/SourceFloatCaller.h b/src/common/SourceFloatCaller.h
index d5d8469..f3bdaf6 100644
--- a/src/common/SourceFloatCaller.h
+++ b/src/common/SourceFloatCaller.h
@@ -32,6 +32,10 @@
     explicit SourceFloatCaller(int32_t channelCount);
 
     int32_t onProcess(int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SourceFloatCaller";
+    }
 };
 
 }
diff --git a/src/flowgraph/AudioProcessorBase.cpp b/src/flowgraph/AudioProcessorBase.cpp
index 0445069..ef7553b 100644
--- a/src/flowgraph/AudioProcessorBase.cpp
+++ b/src/flowgraph/AudioProcessorBase.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "stdio.h"
 #include <algorithm>
 #include <sys/types.h>
 #include "AudioProcessorBase.h"
@@ -24,8 +25,8 @@
 int32_t AudioProcessorBase::pullData(int64_t framePosition, int32_t numFrames) {
     int32_t frameCount = numFrames;
     // Prevent recursion and multiple execution of nodes.
-    if (framePosition > mLastFramePosition) {
-        mLastFramePosition = framePosition;
+    if (framePosition <= mLastFramePosition && !mBlockRecursion) {
+        mBlockRecursion = true;  // for cyclic graphs
         if (mDataPulledAutomatically) {
             // Pull from all the upstream nodes.
             for (auto &port : mInputPorts) {
@@ -36,10 +37,31 @@
         if (frameCount > 0) {
             frameCount = onProcess(frameCount);
         }
+        mLastFramePosition += frameCount;
+        mBlockRecursion = false;
+        mLastFrameCount = frameCount;
+    } else {
+        frameCount = mLastFrameCount;
     }
     return frameCount;
 }
 
+void AudioProcessorBase::pullReset() {
+    if (!mBlockRecursion) {
+        mBlockRecursion = true; // for cyclic graphs
+        // Pull reset from all the upstream nodes.
+        for (auto &port : mInputPorts) {
+            port.get().pullReset();
+        }
+        mBlockRecursion = false;
+        reset();
+    }
+}
+
+void AudioProcessorBase::reset() {
+    mLastFrameCount = 0;
+}
+
 /***************************************************************************/
 AudioFloatBufferPort::AudioFloatBufferPort(AudioProcessorBase &parent,
                                int32_t samplesPerFrame,
@@ -57,6 +79,10 @@
     return mParent.pullData(framePosition, numFrames);
 }
 
+void AudioFloatOutputPort::pullReset() {
+    mParent.pullReset();
+}
+
 // These need to be in the .cpp file because of forward cross references.
 void AudioFloatOutputPort::connect(AudioFloatInputPort *port) {
     port->connect(this);
@@ -72,6 +98,9 @@
             ? std::min(getFramesPerBuffer(), numFrames)
             : mConnected->pullData(framePosition, numFrames);
 }
+void AudioFloatInputPort::pullReset() {
+    if (mConnected != NULL) mConnected->pullReset();
+}
 
 float *AudioFloatInputPort::getBuffer() {
     if (mConnected == NULL) {
diff --git a/src/flowgraph/AudioProcessorBase.h b/src/flowgraph/AudioProcessorBase.h
index d5875f4..b4097e5 100644
--- a/src/flowgraph/AudioProcessorBase.h
+++ b/src/flowgraph/AudioProcessorBase.h
@@ -79,6 +79,16 @@
      */
     int32_t pullData(int64_t framePosition, int32_t numFrames);
 
+    /**
+     * Recursively reset all the nodes in the graph, starting from a Sink.
+     */
+    void pullReset();
+
+    /**
+     * Reset framePosition counters.
+     */
+    virtual void reset();
+
     void addInputPort(AudioPort &port) {
         mInputPorts.push_back(port);
     }
@@ -100,14 +110,23 @@
         mDataPulledAutomatically = automatic;
     }
 
+    virtual const char *getName() {
+        return "AudioProcessorBase";
+    }
+
+    int64_t getLastFramePosition() {
+        return mLastFramePosition;
+    }
+
 protected:
-    int64_t  mLastFramePosition = -1; // Start at -1 so that the first pull works.
+    int64_t  mLastFramePosition = 0;
 
     std::vector<std::reference_wrapper<AudioPort>> mInputPorts;
 
 private:
-    // FIXME int32_t  mFramesValid = 0; // num valid frames in the block
     bool     mDataPulledAutomatically = true;
+    bool     mBlockRecursion = false;
+    int32_t  mLastFrameCount = 0;
 
 };
 
@@ -136,6 +155,8 @@
 
     virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0;
 
+    virtual void pullReset() {}
+
 protected:
     AudioProcessorBase &mParent;
 
@@ -217,6 +238,9 @@
      */
     int32_t pullData(int64_t framePosition, int32_t numFrames) override;
 
+
+    void pullReset() override;
+
 };
 
 /***************************************************************************/
@@ -283,6 +307,8 @@
      */
     int32_t pullData(int64_t framePosition, int32_t numFrames) override;
 
+    void pullReset() override;
+
 private:
     AudioFloatOutputPort *mConnected = nullptr;
 };
diff --git a/src/flowgraph/ClipToRange.h b/src/flowgraph/ClipToRange.h
index 99b3a95..138c14d 100644
--- a/src/flowgraph/ClipToRange.h
+++ b/src/flowgraph/ClipToRange.h
@@ -57,6 +57,10 @@
     AudioFloatInputPort input;
     AudioFloatOutputPort output;
 
+    const char *getName() override {
+        return "ClipToRange";
+    }
+
 private:
     float mMinimum = kDefaultMinHeadroom;
     float mMaximum = kDefaultMaxHeadroom;
diff --git a/src/flowgraph/ManyToMultiConverter.h b/src/flowgraph/ManyToMultiConverter.h
index 5fb2b2b..8a2c480 100644
--- a/src/flowgraph/ManyToMultiConverter.h
+++ b/src/flowgraph/ManyToMultiConverter.h
@@ -39,6 +39,10 @@
     std::vector<std::unique_ptr<flowgraph::AudioFloatInputPort>> inputs;
     flowgraph::AudioFloatOutputPort output;
 
+    const char *getName() override {
+        return "ManyToMultiConverter";
+    }
+
 private:
 };
 
diff --git a/src/flowgraph/MonoToMultiConverter.h b/src/flowgraph/MonoToMultiConverter.h
index 21972a7..9426e9d 100644
--- a/src/flowgraph/MonoToMultiConverter.h
+++ b/src/flowgraph/MonoToMultiConverter.h
@@ -36,6 +36,10 @@
 
     int32_t onProcess(int32_t numFrames) override;
 
+    const char *getName() override {
+        return "MonoToMultiConverter";
+    }
+
     AudioFloatInputPort input;
     AudioFloatOutputPort output;
 };
diff --git a/src/flowgraph/RampLinear.h b/src/flowgraph/RampLinear.h
index 44debf6..899f9e6 100644
--- a/src/flowgraph/RampLinear.h
+++ b/src/flowgraph/RampLinear.h
@@ -74,6 +74,10 @@
         mLevelTo = level;
     }
 
+    const char *getName() override {
+        return "RampLinear";
+    }
+
     AudioFloatInputPort input;
     AudioFloatOutputPort output;
 
diff --git a/src/flowgraph/SampleRateConverter.cpp b/src/flowgraph/SampleRateConverter.cpp
index 58000b6..a1f34c9 100644
--- a/src/flowgraph/SampleRateConverter.cpp
+++ b/src/flowgraph/SampleRateConverter.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include "common/OboeDebug.h"
 #include "SampleRateConverter.h"
 
 using namespace flowgraph;
@@ -26,14 +25,14 @@
     setDataPulledAutomatically(false);
 }
 
-int32_t SampleRateConverter::checkInputFrames() {
+// Return true if these is a sample available.
+bool SampleRateConverter::isInputAvailable() {
     if (mInputCursor >= mInputValid) {
         mInputValid = input.pullData(mInputFramePosition, input.getFramesPerBuffer());
-        LOGD("SampleRateConverter::checkInputFrames: pulled %d", mInputValid);
         mInputFramePosition += mInputValid;
         mInputCursor = 0;
     }
-    return mInputValid;
+    return (mInputCursor < mInputValid);
 }
 
 const float *SampleRateConverter::getNextInputFrame() {
@@ -44,14 +43,11 @@
 int32_t SampleRateConverter::onProcess(int32_t numFrames) {
     float *outputBuffer = output.getBuffer();
     int32_t channelCount = output.getSamplesPerFrame();
-
     int framesLeft = numFrames;
     while (framesLeft > 0) {
         // Gather input samples as needed.
         while(mPhase >= 1.0) {
-            if (checkInputFrames() <= 0) {
-                break;
-            }
+            if (!isInputAvailable()) break;
             mPhase -= 1.0;
             const float *frame = getNextInputFrame();
             mResampler.writeFrame(frame);
diff --git a/src/flowgraph/SampleRateConverter.h b/src/flowgraph/SampleRateConverter.h
index 1897eb3..dd79d3e 100644
--- a/src/flowgraph/SampleRateConverter.h
+++ b/src/flowgraph/SampleRateConverter.h
@@ -44,14 +44,19 @@
     AudioFloatInputPort input;
     AudioFloatOutputPort output;
 
+    const char *getName() override {
+        return "SampleRateConverter";
+    }
+
 private:
 
-    int32_t checkInputFrames();
+    // Return true if these is a sample available.
+    bool isInputAvailable();
 
     const float *getNextInputFrame();
 
     MultiChannelRateConverter mResampler;
-    double  mPhase = 0.0;
+    double  mPhase = 1.0;
     double  mPhaseIncrement = 1.0;
     int32_t mInputCursor = 0;
     int32_t mInputValid = 0;
diff --git a/src/flowgraph/SinkFloat.cpp b/src/flowgraph/SinkFloat.cpp
index 91c50de..bd3435f 100644
--- a/src/flowgraph/SinkFloat.cpp
+++ b/src/flowgraph/SinkFloat.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include "common/OboeDebug.h"
 #include <algorithm>
 #include <unistd.h>
 #include "AudioProcessorBase.h"
@@ -27,7 +26,7 @@
 }
 
 int32_t SinkFloat::read(int64_t framePosition, void *data, int32_t numFrames) {
-    LOGD("SinkFloat::read(,,%d)", numFrames);
+    // printf("SinkFloat::read(,,%d)\n", numFrames);
     float *floatData = (float *) data;
     int32_t channelCount = input.getSamplesPerFrame();
 
@@ -35,7 +34,7 @@
     while (framesLeft > 0) {
         // Run the graph and pull data through the input port.
         int32_t framesPulled = pullData(framePosition, framesLeft);
-        LOGD("SinkFloat::read: framesLeft = %d, framesPulled = %d", framesLeft, framesPulled);
+        // printf("SinkFloat::read: framesLeft = %d, framesPulled = %d\n", framesLeft, framesPulled);
         if (framesPulled <= 0) {
             break;
         }
@@ -46,5 +45,6 @@
         framesLeft -= framesPulled;
         framePosition += framesPulled;
     }
+    // printf("SinkFloat returning %d\n", numFrames - framesLeft);
     return numFrames - framesLeft;
 }
diff --git a/src/flowgraph/SinkFloat.h b/src/flowgraph/SinkFloat.h
index 9ff31e9..a0335fd 100644
--- a/src/flowgraph/SinkFloat.h
+++ b/src/flowgraph/SinkFloat.h
@@ -34,6 +34,9 @@
 
     int32_t read(int64_t framePosition, void *data, int32_t numFrames) override;
 
+    const char *getName() override {
+        return "SinkFloat";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/src/flowgraph/SinkI16.h b/src/flowgraph/SinkI16.h
index faea5ba..f91903b 100644
--- a/src/flowgraph/SinkI16.h
+++ b/src/flowgraph/SinkI16.h
@@ -32,6 +32,10 @@
     explicit SinkI16(int32_t channelCount);
 
     int32_t read(int64_t framePosition, void *data, int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SinkI16";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/src/flowgraph/SinkI24.h b/src/flowgraph/SinkI24.h
index 93fdadb..980419b 100644
--- a/src/flowgraph/SinkI24.h
+++ b/src/flowgraph/SinkI24.h
@@ -33,6 +33,10 @@
     explicit SinkI24(int32_t channelCount);
 
     int32_t read(int64_t framePosition, void *data, int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SinkI24";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/src/flowgraph/SourceFloat.h b/src/flowgraph/SourceFloat.h
index b87a27b..e89aef1 100644
--- a/src/flowgraph/SourceFloat.h
+++ b/src/flowgraph/SourceFloat.h
@@ -32,6 +32,10 @@
     explicit SourceFloat(int32_t channelCount);
 
     int32_t onProcess(int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SourceFloat";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/src/flowgraph/SourceI16.h b/src/flowgraph/SourceI16.h
index 34055c5..c2c4aac 100644
--- a/src/flowgraph/SourceI16.h
+++ b/src/flowgraph/SourceI16.h
@@ -31,6 +31,10 @@
     explicit SourceI16(int32_t channelCount);
 
     int32_t onProcess(int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SourceI16";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/src/flowgraph/SourceI24.h b/src/flowgraph/SourceI24.h
index 9176506..9610cfc 100644
--- a/src/flowgraph/SourceI24.h
+++ b/src/flowgraph/SourceI24.h
@@ -32,6 +32,10 @@
     explicit SourceI24(int32_t channelCount);
 
     int32_t onProcess(int32_t numFrames) override;
+
+    const char *getName() override {
+        return "SourceI24";
+    }
 };
 
 } /* namespace flowgraph */
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a254ecc..2b73f53 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,10 +28,10 @@
 		testOboe
         testUtilities.cpp
 		testFlowgraph.cpp
-#        testStreamClosedMethods.cpp
-#        testStreamStates.cpp
-#        testStreamWaitState.cpp
-#		testStreamOpen.cpp
-#        testXRunBehaviour.cpp
+        testStreamClosedMethods.cpp
+        testStreamStates.cpp
+        testStreamWaitState.cpp
+		testStreamOpen.cpp
+        testXRunBehaviour.cpp
         )
 target_link_libraries(testOboe gtest oboe)
diff --git a/tests/testFlowgraph.cpp b/tests/testFlowgraph.cpp
index eb63ef9..3ae42a7 100644
--- a/tests/testFlowgraph.cpp
+++ b/tests/testFlowgraph.cpp
@@ -18,6 +18,8 @@
  * Test FlowGraph
  */
 
+#include "stdio.h"
+
 #include <iostream>
 
 #include <gtest/gtest.h>
@@ -163,12 +165,14 @@
 //void foo() {
     static const double rateScaler = 0.5;
     static const float input[] = {0.0, 1.0, 2.0};
-    static const float expected[] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.5};
+    static const float expected[] = {0.0, 0.0, 0.0, 0.5, 1.0, 1.5};
     float output[100];
     SourceFloat sourceFloat{1};
     SampleRateConverter resampler{1};
     SinkFloat sinkFloat{1};
 
+    // printf("test_flowgraph::module_sample_rate_converter ===========================\n");
+
     resampler.setPhaseIncrement(rateScaler);
 
     int numInputFrames = sizeof(input) / sizeof(input[0]);
@@ -179,12 +183,16 @@
 
     int numExpectedFrames = sizeof(expected) / sizeof(expected[0]);
     int numOutputFrames = sizeof(output) / sizeof(output[0]);
+    // printf("test_flowgraph::module_sample_rate_converter first read -------------\n");
     int32_t numRead = sinkFloat.read(0 /* framePosition */, output, numOutputFrames);
     EXPECT_EQ(numExpectedFrames, numRead);
     constexpr float tolerance = 0.000001f; // arbitrary
     for (int i = 0; i < numRead; i++) {
         EXPECT_NEAR(expected[i], output[i], tolerance);
+        // printf("test_flowgraph::module_sample_rate_converter output = %f\n", output[i]);
     }
+
+    // printf("test_flowgraph::module_sample_rate_converter second read -------------\n");
     numRead = sinkFloat.read(numRead /* framePosition */, output, numOutputFrames);
     EXPECT_EQ(0, numRead);
 }
\ No newline at end of file