Merge branch 'master' into flowgraph
diff --git a/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.cpp b/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.cpp
deleted file mode 100644
index 3d9d012..0000000
--- a/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-/*
- * AudioProcessor.h
- *
- * Processing node in an audio graph.
- */
-#include <sys/types.h>
-#define MODULE_NAME "NatRingMole"
-#include "common/OboeDebug.h"
-#include "oboe/Oboe.h"
-#include "AudioProcessorBase.h"
-
-AudioPort::AudioPort(AudioProcessorBase &parent, int samplesPerFrame)
-        : mParent(parent)
-        , mSamplesPerFrame(samplesPerFrame) {
-}
-
-AudioFloatPort::AudioFloatPort(AudioProcessorBase &parent, int samplesPerFrame)
-        : AudioPort(parent, samplesPerFrame), mFloatBuffer(NULL) {
-    int numFloats = MAX_BLOCK_SIZE * mSamplesPerFrame;
-    mFloatBuffer = new float[numFloats]{};
-}
-
-AudioFloatPort::~AudioFloatPort() {
-    delete[] mFloatBuffer;
-}
-
-float *AudioFloatPort::getFloatBuffer(int numFrames) {
-    assert(numFrames <= MAX_BLOCK_SIZE);
-    return mFloatBuffer;
-}
-
-AudioOutputPort::AudioOutputPort(AudioProcessorBase &parent, int samplesPerFrame)
-            : AudioFloatPort(parent, samplesPerFrame)
-{
-    LOGD("AudioOutputPort(%d)", samplesPerFrame);
-}
-
-AudioResult AudioOutputPort::pullData(
-        uint64_t framePosition,
-        int numFrames) {
-    return mParent.pullData(framePosition, numFrames);
-}
-
-void AudioOutputPort::connect(AudioInputPort *port) {
-    port->connect(this);
-}
-void AudioOutputPort::disconnect(AudioInputPort *port) {
-    port->disconnect(this);
-}
-
-AudioInputPort::AudioInputPort(AudioProcessorBase &parent, int samplesPerFrame)
-        : AudioFloatPort(parent, samplesPerFrame)
-{
-}
-
-AudioResult AudioInputPort::pullData(
-        uint64_t framePosition,
-        int numFrames) {
-    return (mConnected == nullptr)
-        ? AUDIO_RESULT_SUCCESS
-        : mConnected->pullData(framePosition, numFrames);
-}
-
-float *AudioInputPort::getFloatBuffer(int numFrames) {
-    if (mConnected == NULL) {
-        return AudioFloatPort::getFloatBuffer(numFrames);
-    } else {
-        return mConnected->getFloatBuffer(numFrames);
-    }
-}
-
-void AudioInputPort::setValue(float value) {
-    int numFloats = MAX_BLOCK_SIZE * mSamplesPerFrame;
-    for (int i = 0; i < numFloats; i++) {
-        mFloatBuffer[i] = value;
-    }
-}
-
-/*
- * AudioProcessorBase
- */
-
-AudioResult AudioProcessorBase::pullData(
-        uint64_t framePosition,
-        int numFrames) {
-    if (framePosition > mLastFramePosition) {
-        mLastFramePosition = framePosition;
-        mPreviousResult = onProcess(framePosition, numFrames);
-    }
-    return mPreviousResult;
-}
diff --git a/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.h b/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.h
deleted file mode 100644
index 5cd4cd3..0000000
--- a/apps/OboeTester/app/src/main/cpp/AudioProcessorBase.h
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-/*
- * AudioProcessor.h
- *
- * Processing node in an audio graph.
- */
-
-#ifndef AUDIOPROCESSOR_H_
-#define AUDIOPROCESSOR_H_
-
-#include <cassert>
-#include <cstring>
-#include <math.h>
-#include <jni.h>
-#include <unistd.h>
-#include <time.h>
-
-#include <sys/types.h>
-
-#define MAX_BLOCK_SIZE   2048
-
-class AudioProcessorBase;
-
-class AudioInputPort;
-
-typedef int32_t AudioResult;
-#define AUDIO_RESULT_SUCCESS      0
-#define AUDIO_RESULT_FAIL      -100
-
-
-class AudioPort {
-public:
-    AudioPort(AudioProcessorBase &mParent, int samplesPerFrame);
-
-    int getSamplesPerFrame() const { return mSamplesPerFrame; }
-
-protected:
-    AudioProcessorBase &mParent;
-    int mSamplesPerFrame;
-};
-
-class AudioFloatPort  : public AudioPort {
-public:
-    AudioFloatPort(AudioProcessorBase &mParent, int samplesPerFrame);
-
-    virtual ~AudioFloatPort();
-
-    virtual float *getFloatBuffer(int numFrames);
-
-protected:
-    float   *mFloatBuffer;
-};
-
-class AudioOutputPort : public AudioFloatPort {
-public:
-    AudioOutputPort(AudioProcessorBase &parent, int samplesPerFrame);
-
-    virtual ~AudioOutputPort() = default;
-
-    using AudioFloatPort::getFloatBuffer;
-
-    AudioResult pullData(
-            uint64_t framePosition,
-            int numFrames);
-
-    void connect(AudioInputPort *port);
-    void disconnect(AudioInputPort *port);
-};
-
-class AudioInputPort : public AudioFloatPort {
-public:
-    AudioInputPort(AudioProcessorBase &parent, int mSamplesPerFrame);
-
-    virtual ~AudioInputPort() = default;
-
-    float *getFloatBuffer(int numFrames);
-
-    AudioResult pullData(
-            uint64_t framePosition,
-            int numFrames);
-
-    void connect(AudioOutputPort *port) {
-        assert(getSamplesPerFrame() == port->getSamplesPerFrame());
-        mConnected = port;
-    }
-    void disconnect(AudioOutputPort *port) {
-        assert(mConnected == port);
-        mConnected = NULL;
-    }
-    void disconnect() {
-        mConnected = NULL;
-    }
-
-    void setValue(float value);
-
-private:
-    AudioOutputPort *mConnected = nullptr;
-};
-
-
-class IAudioProcessor {
-public:
-    virtual AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames) = 0;
-};
-
-class AudioProcessorBase : public IAudioProcessor {
-public:
-    virtual ~AudioProcessorBase() = default;
-
-    virtual AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames) = 0;
-
-    AudioResult pullData(
-            uint64_t framePosition,
-            int numFrames);
-
-    virtual void start() {
-        mLastFramePosition = 0;
-    }
-
-    virtual void stop() {}
-
-private:
-    uint64_t    mLastFramePosition = 0;
-    AudioResult mPreviousResult = AUDIO_RESULT_SUCCESS;
-};
-
-#endif /* AUDIOPROCESSOR_H_ */
diff --git a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
index f67a5d7..2a3e204 100644
--- a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
+++ b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.cpp
@@ -21,9 +21,9 @@
 #include "oboe/Oboe.h"
 #include "AudioStreamGateway.h"
 
+using namespace flowgraph;
+
 AudioStreamGateway::AudioStreamGateway(int samplesPerFrame)
-        : input(*this, samplesPerFrame)
-        , mFramePosition(0)
 {
 }
 
@@ -35,60 +35,17 @@
         oboe::AudioStream *audioStream,
         void *audioData,
         int numFrames) {
-    int framesLeft = numFrames;
-    int16_t *shortData = (int16_t *) audioData;
-    float *floatData = (float *) audioData;
-    AudioResult result = 0;
-
-#ifdef OBOE_TESTER_DEBUG_STOP
-    if (mFrameCountdown <= 0) {
-        LOGI("%s() : mCallCounter = %d\n", __func__, mCallCounter);
-        mFrameCountdown = 4800;
-    }
-    mFrameCountdown -= numFrames;
-    mCallCounter++;
-    if (mCallCounter > 500) {
-        LOGI("%s() : return STOP\n", __func__);
-        return oboe::DataCallbackResult::Stop;
-    }
-#endif /* OBOE_TESTER_DEBUG_STOP */
 
     if (!mSchedulerChecked) {
         mScheduler = sched_getscheduler(gettid());
         mSchedulerChecked = true;
     }
 
-    while (framesLeft > 0) {
-        // Do not process more than the MAX block size in one pass.
-        int framesToPlay = framesLeft;
-        if (framesToPlay > MAX_BLOCK_SIZE) {
-            framesToPlay = MAX_BLOCK_SIZE;
-        }
-        // Run the graph and pull data through the input port.
-        result = onProcess(mFramePosition, framesToPlay);
-        if (result < 0) {
-            break;
-        }
-        const float *signal = input.getFloatBuffer(framesToPlay);
-        int32_t numSamples = framesToPlay * input.getSamplesPerFrame();
-        if (audioStream->getFormat() == oboe::AudioFormat::I16) {
-            oboe::convertFloatToPcm16(signal, shortData, numSamples);
-            shortData += numSamples;
-        } else if (audioStream->getFormat() == oboe::AudioFormat::Float) {
-            memcpy(floatData, signal, numSamples * sizeof(float));
-            floatData += numSamples;
-        }
-        mFramePosition += framesToPlay;
-        framesLeft -= framesToPlay;
+    if (mAudioSink != nullptr) {
+        mAudioSink->read(audioData, numFrames);
     }
-    return (result < 0) ? oboe::DataCallbackResult::Stop : oboe::DataCallbackResult::Continue;
-}
 
-AudioResult AudioStreamGateway::onProcess(
-        uint64_t framePosition,
-        int numFrames) {
-    AudioResult result = input.pullData(framePosition, numFrames);
-    return result;
+    return oboe::DataCallbackResult::Continue;
 }
 
 int AudioStreamGateway::getScheduler() {
diff --git a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
index fe22b50..09d69c6 100644
--- a/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
+++ b/apps/OboeTester/app/src/main/cpp/AudioStreamGateway.h
@@ -19,28 +19,24 @@
 #include <unistd.h>
 #include <sys/types.h>
 
-#include "AudioProcessorBase.h"
+#include "flowgraph/AudioProcessorBase.h"
 #include "oboe/Oboe.h"
 
+using namespace flowgraph;
+
 /**
  * Bridge between an audio graph and an audio device.
- * Connect the audio units to the "input" and then pass
+ * Pass in an AudioSink and then pass
  * this object to the AudioStreamBuilder as a callback.
  */
-class AudioStreamGateway : public AudioProcessorBase, public oboe::AudioStreamCallback {
+class AudioStreamGateway : public oboe::AudioStreamCallback {
 public:
     AudioStreamGateway(int samplesPerFrame);
     virtual ~AudioStreamGateway();
 
-    /**
-     * Process audio for the graph.
-     * @param framePosition
-     * @param numFrames
-     * @return
-     */
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames) override;
+    void setAudioSink(std::shared_ptr<flowgraph::AudioSink>  sink) {
+        mAudioSink = sink;
+    }
 
     /**
      * Called by Oboe when the stream is ready to process audio.
@@ -50,23 +46,13 @@
             void *audioData,
             int numFrames) override;
 
-    AudioInputPort input;
-
     int getScheduler();
 
-    void start() override {
-        AudioProcessorBase::start();
-        mCallCounter = 0;
-        mFrameCountdown = 0;
-    }
-
 private:
-    uint64_t mFramePosition;
+    // TODO uint64_t mFramePosition;
     bool     mSchedulerChecked = false;
     int      mScheduler;
-
-    int32_t  mCallCounter = 0;
-    int32_t  mFrameCountdown = 0;
+    std::shared_ptr<flowgraph::AudioSink>  mAudioSink;
 };
 
 
diff --git a/apps/OboeTester/app/src/main/cpp/FifoProcessor.cpp b/apps/OboeTester/app/src/main/cpp/FifoProcessor.cpp
deleted file mode 100644
index f7478bf..0000000
--- a/apps/OboeTester/app/src/main/cpp/FifoProcessor.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2016 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 "FifoProcessor.h"
-
-
-FifoProcessor::FifoProcessor(int channelCount, int numFrames, int threshold)
-        : mFifoBuffer(channelCount, numFrames)
-        , output(*this, channelCount)
-{
-    mFifoBuffer.setThresholdFrames(threshold);
-}
-
-FifoProcessor::~FifoProcessor() {
-}
diff --git a/apps/OboeTester/app/src/main/cpp/FifoProcessor.h b/apps/OboeTester/app/src/main/cpp/FifoProcessor.h
deleted file mode 100644
index a562b79..0000000
--- a/apps/OboeTester/app/src/main/cpp/FifoProcessor.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2016 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 NATIVEOBOE_FIFIPROCESSOR_H
-#define NATIVEOBOE_FIFIPROCESSOR_H
-
-#include "AudioProcessorBase.h"
-#include "fifo/FifoBuffer.h"
-
-class FifoProcessor : public AudioProcessorBase {
-public:
-    FifoProcessor(int samplesPerFrame, int numFrames, int threshold);
-
-    virtual ~FifoProcessor();
-
-    uint32_t read(float *destination, int framesToRead) {
-        return mFifoBuffer.read(destination, framesToRead);
-    }
-
-    uint32_t write(const float *source, int framesToWrite) {
-        return mFifoBuffer.write(source, framesToWrite);
-    }
-
-    uint32_t getThresholdFrames() {
-        return mFifoBuffer.getThresholdFrames();
-    }
-
-    void setThresholdFrames(uint32_t threshold) {
-        return mFifoBuffer.setThresholdFrames(threshold);
-    }
-
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames)  override {
-        float *buffer = output.getFloatBuffer(numFrames);
-        return mFifoBuffer.readNow(buffer, numFrames);
-    }
-
-    uint32_t getUnderrunCount() const { return mFifoBuffer.getUnderrunCount(); }
-
-private:
-    oboe::FifoBuffer  mFifoBuffer;
-
-public:
-    AudioOutputPort output;
-
-};
-
-
-#endif //NATIVEOBOE_FIFIPROCESSOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
index ddcce09..fd0aee0 100644
--- a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
+++ b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
@@ -21,7 +21,7 @@
 #include <unistd.h>
 #include <sys/types.h>
 
-#include "AudioProcessorBase.h"
+// TODO #include "flowgraph/AudioProcessorBase.h"
 #include "oboe/Oboe.h"
 #include "MultiChannelRecording.h"
 #include "PeakDetector.h"
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
index 3ec5f3c..72216de 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
@@ -20,7 +20,8 @@
 
 
 NativeAudioContext::NativeAudioContext()
-    : sineGenerators(MAX_SINE_OSCILLATORS) {
+    : sineOscillators(MAX_SINE_OSCILLATORS)
+    , sawtoothOscillators(MAX_SINE_OSCILLATORS) {
 }
 
 void NativeAudioContext::close() {
@@ -34,6 +35,8 @@
     manyToMulti.reset(nullptr);
     monoToMulti.reset(nullptr);
     audioStreamGateway.reset(nullptr);
+    mSinkFloat.reset();
+    mSinkI16.reset();
 }
 
 bool NativeAudioContext::isMMapUsed() {
@@ -65,17 +68,27 @@
         switch (mToneType) {
             case ToneType::SawPing:
                 sawPingGenerator.output.connect(&(monoToMulti->input));
-                monoToMulti->output.connect(&(audioStreamGateway.get()->input));
+                monoToMulti->output.connect(&(mSinkFloat.get()->input));
+                monoToMulti->output.connect(&(mSinkI16.get()->input));
                 break;
             case ToneType::Sine:
                 for (int i = 0; i < mChannelCount; i++) {
-                    sineGenerators[i].output.connect(manyToMulti->inputs[i].get());
+                    sineOscillators[i].output.connect(manyToMulti->inputs[i].get());
                 }
-                manyToMulti->output.connect(&(audioStreamGateway.get()->input));
+                manyToMulti->output.connect(&(mSinkFloat.get()->input));
+                manyToMulti->output.connect(&(mSinkI16.get()->input));
                 break;
             case ToneType::Impulse:
                 impulseGenerator.output.connect(&(monoToMulti->input));
-                monoToMulti->output.connect(&(audioStreamGateway.get()->input));
+                monoToMulti->output.connect(&(mSinkFloat.get()->input));
+                monoToMulti->output.connect(&(mSinkI16.get()->input));
+                break;
+            case ToneType::Sawtooth:
+                for (int i = 0; i < mChannelCount; i++) {
+                    sawtoothOscillators[i].output.connect(manyToMulti->inputs[i].get());
+                }
+                manyToMulti->output.connect(&(mSinkFloat.get()->input));
+                manyToMulti->output.connect(&(mSinkI16.get()->input));
                 break;
         }
     }
@@ -86,7 +99,16 @@
         return;
     }
     if (enabled) {
-        sineGenerators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
+        switch (mToneType) {
+            case ToneType::Sine:
+                sineOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
+                break;
+            case ToneType::Sawtooth:
+                sawtoothOscillators[channelIndex].output.connect(manyToMulti->inputs[channelIndex].get());
+                break;
+            default:
+                break;
+        }
     } else {
         manyToMulti->inputs[channelIndex]->disconnect();
     }
@@ -152,10 +174,16 @@
         } else {
             double frequency = 440.0;
             for (int i = 0; i < mChannelCount; i++) {
-                sineGenerators[i].setSampleRate(oboeStream->getSampleRate());
-                sineGenerators[i].frequency.setValue(frequency);
-                frequency *= 4.0 / 3.0; // each sine is a higher frequency
-                sineGenerators[i].amplitude.setValue(AMPLITUDE_SINE);
+                sineOscillators[i].setSampleRate(oboeStream->getSampleRate());
+                sineOscillators[i].frequency.setValue(frequency);
+                frequency *= 4.0 / 3.0; // each sine is at a higher frequency
+                sineOscillators[i].amplitude.setValue(AMPLITUDE_SINE);
+            }
+            for (int i = 0; i < mChannelCount; i++) {
+                sawtoothOscillators[i].setSampleRate(oboeStream->getSampleRate());
+                sawtoothOscillators[i].frequency.setValue(frequency);
+                frequency *= 4.0 / 3.0; // each sawtooth is at a higher frequency
+                sawtoothOscillators[i].amplitude.setValue(AMPLITUDE_SAWTOOTH);
             }
 
             impulseGenerator.setSampleRate(oboeStream->getSampleRate());
@@ -169,9 +197,17 @@
             manyToMulti = std::make_unique<ManyToMultiConverter>(mChannelCount);
             monoToMulti = std::make_unique<MonoToMultiConverter>(mChannelCount);
 
+            mSinkFloat = std::make_unique<SinkFloat>(mChannelCount);
+            mSinkI16 = std::make_unique<SinkI16>(mChannelCount);
+
             // We needed the proxy because we did not know the channelCount
             // when we setup the Builder.
             audioStreamGateway = std::make_unique<AudioStreamGateway>(mChannelCount);
+            if (oboeStream->getFormat() == oboe::AudioFormat::I16) {
+                audioStreamGateway->setAudioSink(mSinkI16);
+            } else if (oboeStream->getFormat() == oboe::AudioFormat::Float) {
+                audioStreamGateway->setAudioSink(mSinkFloat);
+            }
 
             connectTone();
 
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
index df6ac36..4d389ea 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
@@ -18,6 +18,7 @@
 #define NATIVEOBOE_NATIVEAUDIOCONTEXT_H
 
 #include <dlfcn.h>
+#include <jni.h>
 #include <thread>
 #include <vector>
 
@@ -25,20 +26,24 @@
 #include "oboe/Oboe.h"
 
 #include "AudioStreamGateway.h"
-#include "ImpulseGenerator.h"
+#include "flowgraph/ImpulseOscillator.h"
+#include "flowgraph/ManyToMultiConverter.h"
+#include "flowgraph/MonoToMultiConverter.h"
+#include "flowgraph/SinkFloat.h"
+#include "flowgraph/SinkI16.h"
+#include "flowgraph/SineOscillator.h"
+#include "flowgraph/SawtoothOscillator.h"
 #include "InputStreamCallbackAnalyzer.h"
-#include "ManyToMultiConverter.h"
-#include "MonoToMultiConverter.h"
 #include "MultiChannelRecording.h"
 #include "OboeStreamCallbackProxy.h"
 #include "PlayRecordingCallback.h"
 #include "SawPingGenerator.h"
-#include "SineGenerator.h"
 
 #define MAX_SINE_OSCILLATORS     8
 #define AMPLITUDE_SINE           1.0
+#define AMPLITUDE_SAWTOOTH       0.5
 #define FREQUENCY_SAW_PING       800.0
-#define AMPLITUDE_SAW_PING       1.0
+#define AMPLITUDE_SAW_PING       0.8
 #define AMPLITUDE_IMPULSE        0.7
 
 #define NANOS_PER_MICROSECOND    ((int64_t) 1000)
@@ -109,6 +114,7 @@
             result = oboeStream->requestStop();
             printScheduler();
         }
+        LOGD("NativeAudioContext::%s() returns %d", __func__, result);
         return result;
     }
 
@@ -129,15 +135,24 @@
         oboe::Result result1 = stopPlayback();
         oboe::Result result2 = stopAudio();
 
+        LOGD("NativeAudioContext::%s() stop modules", __func__);
         for (int i = 0; i < mChannelCount; i++) {
-            sineGenerators[i].stop();
+            sineOscillators[i].stop();
+            sawtoothOscillators[i].stop();
         }
         impulseGenerator.stop();
         sawPingGenerator.stop();
-        if (audioStreamGateway != nullptr) {
-            audioStreamGateway->stop();
+        if (mSinkFloat) {
+            mSinkFloat->stop();
         }
-        return (result1 != oboe::Result::OK) ? result1 : result2;
+        if (mSinkI16) {
+            mSinkI16->stop();
+        }
+
+        oboe::Result result = (result1 != oboe::Result::OK) ? result1 : result2;
+
+        LOGD("NativeAudioContext::%s() returns %d", __func__, result);
+        return result;
     }
 
     oboe::Result startPlayback() {
@@ -171,22 +186,30 @@
     }
 
     oboe::Result start() {
+
+        LOGD("NativeAudioContext: %s() called", __func__);
+
         if (oboeStream == nullptr) {
             return oboe::Result::ErrorInvalidState;
         }
 
         stop();
 
+        LOGD("NativeAudioContext: %s() start modules", __func__);
         for (int i = 0; i < mChannelCount; i++) {
-            sineGenerators[i].start();
+            sineOscillators[i].start();
+            sawtoothOscillators[i].start();
         }
         impulseGenerator.start();
         sawPingGenerator.start();
-        if (audioStreamGateway != nullptr) {
-            audioStreamGateway->start();
+        if (mSinkFloat) {
+            mSinkFloat->start();
+        }
+        if (mSinkI16) {
+            mSinkI16->start();
         }
 
-        LOGD("OboeAudioStream_start: start called");
+        LOGD("NativeAudioContext: %s start stream", __func__);
         oboe::Result result = oboe::Result::OK;
         if (oboeStream != nullptr) {
             result = oboeStream->requestStart();
@@ -216,7 +239,8 @@
     void setAmplitude(double amplitude) {
         LOGD("%s(%f)", __func__, amplitude);
         for (int i = 0; i < mChannelCount; i++) {
-            sineGenerators[i].amplitude.setValue(amplitude);
+            sineOscillators[i].amplitude.setValue(amplitude);
+            sawtoothOscillators[i].amplitude.setValue(amplitude);
         }
         sawPingGenerator.amplitude.setValue(amplitude);
         impulseGenerator.amplitude.setValue(amplitude);
@@ -245,7 +269,8 @@
     enum ToneType {
         SawPing = 0,
         Sine = 1,
-        Impulse = 2
+        Impulse = 2,
+        Sawtooth = 3
     };
 
     void connectTone();
@@ -274,15 +299,18 @@
     std::thread                 *dataThread = nullptr;
 
     OboeStreamCallbackProxy      oboeCallbackProxy;
-    std::vector<SineGenerator>   sineGenerators;
+    std::vector<SineOscillator>  sineOscillators;
+    std::vector<SawtoothOscillator>  sawtoothOscillators;
 
-    ImpulseGenerator             impulseGenerator;
+    ImpulseOscillator            impulseGenerator;
     SawPingGenerator             sawPingGenerator;
     oboe::AudioStream           *playbackStream = nullptr;
 
     std::unique_ptr<float []>               dataBuffer{};
     std::unique_ptr<ManyToMultiConverter>   manyToMulti;
     std::unique_ptr<MonoToMultiConverter>   monoToMulti;
+    std::shared_ptr<flowgraph::SinkFloat>   mSinkFloat;
+    std::shared_ptr<flowgraph::SinkI16>     mSinkI16;
     std::unique_ptr<AudioStreamGateway>     audioStreamGateway{};
     std::unique_ptr<MultiChannelRecording>  mRecording{};
 
diff --git a/apps/OboeTester/app/src/main/cpp/OscillatorBase.h b/apps/OboeTester/app/src/main/cpp/OscillatorBase.h
deleted file mode 100644
index cf2a605..0000000
--- a/apps/OboeTester/app/src/main/cpp/OscillatorBase.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2018 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 NATIVEOBOE_OSCILLATORBASE_H
-#define NATIVEOBOE_OSCILLATORBASE_H
-
-#include "AudioProcessorBase.h"
-
-constexpr float TWO_PI = (float)(2.0 * M_PI);
-
-class OscillatorBase : public AudioProcessorBase {
-public:
-    OscillatorBase();
-
-    virtual ~OscillatorBase() = default;
-
-    void setSampleRate(float sampleRate) {
-        mSampleRate = sampleRate;
-        mFrequencyToPhase = TWO_PI / sampleRate; // scaler
-    }
-
-    float setSampleRate() {
-        return mSampleRate;
-    }
-
-    AudioInputPort  frequency;
-    AudioInputPort  amplitude;
-    AudioOutputPort output;
-
-protected:
-    float incrementPhase(float frequency) {
-        mPhase += frequency * mFrequencyToPhase;
-        if (mPhase >= TWO_PI) {
-            mPhase -= TWO_PI;
-        } else if (mPhase < 0) {
-            mPhase += TWO_PI;
-        }
-        return mPhase;
-    }
-
-    float   mPhase = 0.0;
-    float   mSampleRate;
-    float   mFrequencyToPhase;
-};
-
-
-#endif //NATIVEOBOE_OSCILLATORBASE_H
diff --git a/apps/OboeTester/app/src/main/cpp/SawPingGenerator.cpp b/apps/OboeTester/app/src/main/cpp/SawPingGenerator.cpp
index 01f4dcf..6b128c0 100644
--- a/apps/OboeTester/app/src/main/cpp/SawPingGenerator.cpp
+++ b/apps/OboeTester/app/src/main/cpp/SawPingGenerator.cpp
@@ -15,9 +15,10 @@
  */
 
 #include <unistd.h>
-#include "AudioProcessorBase.h"
-#include "SawPingGenerator.h"
 #include "oboe/Definitions.h"
+#include "SawPingGenerator.h"
+
+using namespace flowgraph;
 
 SawPingGenerator::SawPingGenerator()
         : OscillatorBase()
@@ -28,18 +29,16 @@
 
 SawPingGenerator::~SawPingGenerator() { }
 
-
-AudioResult SawPingGenerator::onProcess(
-        uint64_t framePosition,
+int32_t SawPingGenerator::onProcess(
+        int64_t framePosition,
         int numFrames) {
 
     frequency.pullData(framePosition, numFrames);
     amplitude.pullData(framePosition, numFrames);
 
-    const float *frequencies = frequency.getFloatBuffer(numFrames);
-    const float *amplitudes = amplitude.getFloatBuffer(numFrames);
-    float *buffer = output.getFloatBuffer(numFrames);
-
+    const float *frequencies = frequency.getBlock();
+    const float *amplitudes = amplitude.getBlock();
+    float *buffer = output.getBlock();
 
     if (mRequestCount.load() > mAcknowledgeCount.load()) {
         mPhase = -1.0f;
@@ -60,7 +59,7 @@
         }
     }
 
-    return AUDIO_RESULT_SUCCESS;
+    return numFrames;
 }
 
 void SawPingGenerator::setEnabled(bool enabled) {
diff --git a/apps/OboeTester/app/src/main/cpp/SawPingGenerator.h b/apps/OboeTester/app/src/main/cpp/SawPingGenerator.h
index c51387e..05bb745 100644
--- a/apps/OboeTester/app/src/main/cpp/SawPingGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/SawPingGenerator.h
@@ -21,8 +21,8 @@
 #include <unistd.h>
 #include <sys/types.h>
 
-#include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
+#include "flowgraph/AudioProcessorBase.h"
+#include "flowgraph/OscillatorBase.h"
 
 class SawPingGenerator : public OscillatorBase {
 public:
@@ -30,8 +30,8 @@
 
     virtual ~SawPingGenerator();
 
-    AudioResult onProcess(
-            uint64_t framePosition,
+    int32_t onProcess(
+            int64_t framePosition,
             int numFrames) override;
 
     void setEnabled(bool enabled);
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp
new file mode 100644
index 0000000..5667fdb
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2015 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 <algorithm>
+#include <sys/types.h>
+#include "AudioProcessorBase.h"
+
+using namespace flowgraph;
+
+/***************************************************************************/
+int32_t AudioProcessorBase::pullData(int64_t framePosition, int32_t numFrames) {
+    if (framePosition > mLastFramePosition) {
+        mLastFramePosition = framePosition;
+        mFramesValid = onProcess(framePosition, numFrames);
+    }
+    return mFramesValid;
+}
+
+/***************************************************************************/
+AudioFloatBlockPort::AudioFloatBlockPort(AudioProcessorBase &parent,
+                               int32_t samplesPerFrame,
+                               int32_t framesPerBlock)
+        : AudioPort(parent, samplesPerFrame)
+        , mFramesPerBlock(framesPerBlock)
+        , mSampleBlock(NULL) {
+    int32_t numFloats = framesPerBlock * getSamplesPerFrame();
+    mSampleBlock = new float[numFloats]{0.0f};
+}
+
+AudioFloatBlockPort::~AudioFloatBlockPort() {
+    delete[] mSampleBlock;
+}
+
+/***************************************************************************/
+int32_t AudioFloatOutputPort::pullData(int64_t framePosition, int32_t numFrames) {
+    numFrames = std::min(getFramesPerBlock(), numFrames);
+    return mParent.pullData(framePosition, numFrames);
+}
+
+// These need to be in the .cpp file because of forward cross references.
+void AudioFloatOutputPort::connect(AudioFloatInputPort *port) {
+    port->connect(this);
+}
+
+void AudioFloatOutputPort::disconnect(AudioFloatInputPort *port) {
+    port->disconnect(this);
+}
+
+/***************************************************************************/
+int32_t AudioFloatInputPort::pullData(int64_t framePosition, int32_t numFrames) {
+    return (mConnected == NULL)
+            ? std::min(getFramesPerBlock(), numFrames)
+            : mConnected->pullData(framePosition, numFrames);
+}
+
+float *AudioFloatInputPort::getBlock() {
+    if (mConnected == NULL) {
+        return AudioFloatBlockPort::getBlock(); // loaded using setValue()
+    } else {
+        return mConnected->getBlock();
+    }
+}
+
+/***************************************************************************/
+int32_t AudioSink::pull(int32_t numFrames) {
+    int32_t actualFrames = input.pullData(mFramePosition, numFrames);
+    mFramePosition += actualFrames;
+    return actualFrames;
+}
\ No newline at end of file
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.h b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.h
new file mode 100644
index 0000000..3c5fe4d
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/AudioProcessorBase.h
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/*
+ * AudioProcessorBase.h
+ *
+ * Audio processing node and ports that can be used in a simple data flow graph.
+ */
+
+#ifndef FLOWGRAPH_AUDIO_PROCESSOR_BASE_H
+#define FLOWGRAPH_AUDIO_PROCESSOR_BASE_H
+
+#include <cassert>
+#include <cstring>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+// Set this to 1 if using it inside the Android framework.
+#define FLOWGRAPH_ANDROID_INTERNAL 0
+
+namespace flowgraph {
+
+// Default block size that can be overridden when the AudioFloatBlockPort is created.
+// If it is too small then we will have too much overhead from switching between nodes.
+// If it is too high then we will thrash the caches.
+constexpr int kDefaultBlockSize = 8; // arbitrary
+
+class AudioFloatInputPort;
+
+/***************************************************************************/
+class AudioProcessorBase {
+public:
+    virtual ~AudioProcessorBase() = default;
+
+    /**
+     * Perform custom function.
+     *
+     * @param framePosition index of first frame to be processed
+     * @param numFrames maximum number of frames requested for processing
+     * @return number of frames actually processed
+     */
+    virtual int32_t onProcess(int64_t framePosition, int32_t numFrames) = 0;
+
+    /**
+     * If the framePosition is at or after the last frame position then call onProcess().
+     * This prevents infinite recursion in case of cyclic graphs.
+     * It also prevents nodes upstream from a branch from being executed twice.
+     *
+     * @param framePosition
+     * @param numFrames
+     * @return number of frames valid
+     */
+    int32_t pullData(int64_t framePosition, int32_t numFrames);
+
+    virtual void start() {
+        mLastFramePosition = 0;
+    }
+
+    virtual void stop() {}
+
+protected:
+    int64_t  mLastFramePosition = -1; // Start at -1 so that the first pull works.
+
+private:
+    int32_t  mFramesValid = 0; // num valid frames in the block
+};
+
+/***************************************************************************/
+/**
+  * This is a connector that allows data to flow between modules.
+  *
+  * The ports are the primary means of interacting with a module.
+  * So they are generally declared as public.
+  *
+  */
+class AudioPort {
+public:
+    AudioPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
+            : mParent(parent)
+            , mSamplesPerFrame(samplesPerFrame) {
+    }
+
+    // Ports are often declared public. So let's make them non-copyable.
+    AudioPort(const AudioPort&) = delete;
+    AudioPort& operator=(const AudioPort&) = delete;
+
+    int32_t getSamplesPerFrame() const {
+        return mSamplesPerFrame;
+    }
+
+protected:
+    AudioProcessorBase &mParent;
+
+private:
+    const int32_t    mSamplesPerFrame = 1;
+};
+
+/***************************************************************************/
+/**
+ * This port contains a float type buffer.
+ * The size is framesPerBlock * samplesPerFrame).
+ */
+class AudioFloatBlockPort  : public AudioPort {
+public:
+    AudioFloatBlockPort(AudioProcessorBase &mParent,
+                   int32_t samplesPerFrame,
+                   int32_t framesPerBlock = kDefaultBlockSize
+                );
+
+    virtual ~AudioFloatBlockPort();
+
+    int32_t getFramesPerBlock() const {
+        return mFramesPerBlock;
+    }
+
+protected:
+
+    /**
+     * @return buffer internal to the port or from a connected port
+     */
+    virtual float *getBlock() {
+        return mSampleBlock;
+    }
+
+
+private:
+    const int32_t    mFramesPerBlock = 1;
+    float           *mSampleBlock = nullptr; // allocated in constructor
+};
+
+/***************************************************************************/
+/**
+  * The results of a module are stored in the buffer of the output ports.
+  */
+class AudioFloatOutputPort : public AudioFloatBlockPort {
+public:
+    AudioFloatOutputPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
+            : AudioFloatBlockPort(parent, samplesPerFrame) {
+    }
+
+    virtual ~AudioFloatOutputPort() = default;
+
+    using AudioFloatBlockPort::getBlock;
+
+    /**
+     * Call the parent module's onProcess() method.
+     * That may pull data from its inputs and recursively
+     * process the entire graph.
+     * @return number of frames actually pulled
+     */
+    int32_t pullData(int64_t framePosition, int32_t numFrames);
+
+    /**
+     * Connect to the input of another module.
+     * An input port can only have one connection.
+     * An output port can have multiple connections.
+     * If you connect a second output port to an input port
+     * then it overwrites the previous connection.
+     *
+     * This not thread safe. Do not modify the graph topology from another thread while running.
+     * Also do not delete a module while it is connected to another port if the graph is running.
+     */
+    void connect(AudioFloatInputPort *port);
+
+    /**
+     * Disconnect from the input of another module.
+     * This not thread safe.
+     */
+    void disconnect(AudioFloatInputPort *port);
+};
+
+/***************************************************************************/
+class AudioFloatInputPort : public AudioFloatBlockPort {
+public:
+    AudioFloatInputPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
+            : AudioFloatBlockPort(parent, samplesPerFrame) {
+    }
+
+    virtual ~AudioFloatInputPort() = default;
+
+    /**
+     * If connected to an output port then this will return
+     * that output ports buffers.
+     * If not connected then it returns the input ports own buffer
+     * which can be loaded using setValue().
+     */
+    float *getBlock() override;
+
+    /**
+     * Pull data from any output port that is connected.
+     */
+    int32_t pullData(int64_t framePosition, int32_t numFrames);
+
+    /**
+     * Write every value of the float buffer.
+     * This value will be ignored if an output port is connected
+     * to this port.
+     */
+    void setValue(float value) {
+        int numFloats = kDefaultBlockSize * getSamplesPerFrame();
+        float *buffer = getBlock();
+        for (int i = 0; i < numFloats; i++) {
+            *buffer++ = value;
+        }
+    }
+
+    /**
+     * Connect to the output of another module.
+     * An input port can only have one connection.
+     * An output port can have multiple connections.
+     * This not thread safe.
+     */
+    void connect(AudioFloatOutputPort *port) {
+        assert(getSamplesPerFrame() == port->getSamplesPerFrame());
+        mConnected = port;
+    }
+
+    void disconnect(AudioFloatOutputPort *port) {
+        assert(mConnected == port);
+        (void) port;
+        mConnected = nullptr;
+    }
+
+    void disconnect() {
+        mConnected = nullptr;
+    }
+
+private:
+    AudioFloatOutputPort *mConnected = nullptr;
+};
+
+/***************************************************************************/
+class AudioSource : public AudioProcessorBase {
+public:
+    explicit AudioSource(int32_t channelCount)
+            : output(*this, channelCount) {
+    }
+
+    virtual ~AudioSource() = default;
+
+    AudioFloatOutputPort output;
+
+    void setData(const void *data, int32_t numFrames) {
+        mData = data;
+        mSizeInFrames = numFrames;
+        mFrameIndex = 0;
+    }
+
+protected:
+    const void *mData = nullptr;
+    int32_t     mSizeInFrames = 0; // number of frames in mData
+    int32_t     mFrameIndex = 0; // index of next frame to be processed
+};
+
+/***************************************************************************/
+class AudioSink : public AudioProcessorBase {
+public:
+    explicit AudioSink(int32_t channelCount)
+            : input(*this, channelCount) {
+    }
+
+    virtual ~AudioSink() = default;
+
+    AudioFloatInputPort input;
+
+    /**
+     * Dummy processor. The work happens in the read() method.
+     *
+     * @param framePosition index of first frame to be processed
+     * @param numFrames
+     * @return number of frames actually processed
+     */
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override {
+        (void) framePosition;
+        (void) numFrames;
+        return 0;
+    };
+
+    virtual int32_t read(void *data, int32_t numFrames) = 0;
+
+protected:
+    int32_t pull(int32_t numFrames);
+
+private:
+    int64_t mFramePosition = 0;
+};
+
+} /* namespace flowgraph */
+
+#endif /* FLOWGRAPH_AUDIO_PROCESSOR_BASE_H */
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.cpp
new file mode 100644
index 0000000..bd9c22a
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 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 <algorithm>
+#include <unistd.h>
+#include "AudioProcessorBase.h"
+#include "ClipToRange.h"
+
+using namespace flowgraph;
+
+ClipToRange::ClipToRange(int32_t channelCount)
+        : input(*this, channelCount)
+        , output(*this, channelCount) {
+}
+
+int32_t ClipToRange::onProcess(int64_t framePosition, int32_t numFrames) {
+    int32_t framesToProcess = input.pullData(framePosition, numFrames);
+    const float *inputBuffer = input.getBlock();
+    float *outputBuffer = output.getBlock();
+
+    int32_t numSamples = framesToProcess * output.getSamplesPerFrame();
+    for (int32_t i = 0; i < numSamples; i++) {
+        *outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++));
+    }
+
+    return framesToProcess;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.h b/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.h
new file mode 100644
index 0000000..9eef254
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ClipToRange.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 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_CLIP_TO_RANGE_H
+#define FLOWGRAPH_CLIP_TO_RANGE_H
+
+#include <atomic>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "AudioProcessorBase.h"
+
+namespace flowgraph {
+
+// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data.
+// It is designed to allow occasional transient peaks.
+constexpr float kDefaultMaxHeadroom = 1.41253754f;
+constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom;
+
+class ClipToRange : public AudioProcessorBase {
+public:
+    explicit ClipToRange(int32_t channelCount);
+
+    virtual ~ClipToRange() = default;
+
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
+
+    void setMinimum(float min) {
+        mMinimum = min;
+    }
+
+    float getMinimum() const {
+        return mMinimum;
+    }
+
+    void setMaximum(float min) {
+        mMaximum = min;
+    }
+
+    float getMaximum() const {
+        return mMaximum;
+    }
+
+    AudioFloatInputPort input;
+    AudioFloatOutputPort output;
+
+private:
+    float mMinimum = kDefaultMinHeadroom;
+    float mMaximum = kDefaultMaxHeadroom;
+};
+
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_CLIP_TO_RANGE_H
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.cpp
similarity index 62%
rename from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.cpp
rename to apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.cpp
index d621a8c..4847b4e 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.cpp
@@ -16,34 +16,33 @@
 
 #include <math.h>
 #include <unistd.h>
-#include "AudioProcessorBase.h"
 
-#include "ImpulseGenerator.h"
+#include "ImpulseOscillator.h"
 
-ImpulseGenerator::ImpulseGenerator()
+ImpulseOscillator::ImpulseOscillator()
         : OscillatorBase() {
 }
 
-AudioResult ImpulseGenerator::onProcess(
-        uint64_t framePosition,
+int32_t ImpulseOscillator::onProcess(
+        int64_t framePosition,
         int numFrames) {
 
     frequency.pullData(framePosition, numFrames);
     amplitude.pullData(framePosition, numFrames);
 
-    const float *frequencies = frequency.getFloatBuffer(numFrames);
-    const float *amplitudes = amplitude.getFloatBuffer(numFrames);
-    float *buffer = output.getFloatBuffer(numFrames);
+    const float *frequencies = frequency.getBlock();
+    const float *amplitudes = amplitude.getBlock();
+    float *buffer = output.getBlock();
 
     for (int i = 0; i < numFrames; i++) {
         float value = 0.0f;
-        mPhase += mFrequencyToPhase * frequencies[i];
-        if (mPhase >= TWO_PI) {
-            value = amplitudes[i];
-            mPhase -= TWO_PI;
+        mPhase += mFrequencyToPhaseIncrement * frequencies[i];
+        if (mPhase >= 1.0f) {
+            value = amplitudes[i]; // spike
+            mPhase -= 2.0f;
         }
         *buffer++ = value;
     }
 
-    return AUDIO_RESULT_SUCCESS;
+    return numFrames;
 }
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.h
similarity index 70%
rename from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
rename to apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.h
index 5db11b5..2492c1f 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ImpulseOscillator.h
@@ -23,16 +23,19 @@
 #include "AudioProcessorBase.h"
 #include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+/**
+ * Generate a raw impulse equal to the amplitude.
+ * The output baseline is zero.
+ *
+ * The waveform is not band-limited so it will have aliasing artifacts at higher frequencies.
+ */
+class ImpulseOscillator : public OscillatorBase {
 public:
-    ImpulseGenerator();
+    ImpulseOscillator();
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t onProcess(
+            int64_t framePosition,
+            int numFrames) override;
 };
 
 #endif //NATIVEOBOE_IMPULSE_GENERATOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.cpp
similarity index 79%
rename from apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.cpp
rename to apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.cpp
index ba5ad9f..a17ad93 100644
--- a/apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.cpp
@@ -15,19 +15,21 @@
  */
 
 #include <unistd.h>
-#include "AudioProcessorBase.h"
+
 #include "ManyToMultiConverter.h"
 
+using namespace flowgraph;
+
 ManyToMultiConverter::ManyToMultiConverter(int32_t channelCount)
         : inputs(channelCount)
         , output(*this, channelCount) {
     for (int i = 0; i < channelCount; i++) {
-        inputs[i] = std::make_unique<AudioInputPort>(*this, 1);
+        inputs[i] = std::make_unique<AudioFloatInputPort>(*this, 1);
     }
 }
 
-AudioResult ManyToMultiConverter::onProcess(
-        uint64_t framePosition,
+int32_t ManyToMultiConverter::onProcess(
+        int64_t framePosition,
         int numFrames) {
     int32_t channelCount = output.getSamplesPerFrame();
 
@@ -35,10 +37,9 @@
         inputs[i]->pullData(framePosition, numFrames);
     }
 
-
     for (int ch = 0; ch < channelCount; ch++) {
-        const float *inputBuffer = inputs[ch]->getFloatBuffer(numFrames);
-        float *outputBuffer = output.getFloatBuffer(numFrames) + ch;
+        const float *inputBuffer = inputs[ch]->getBlock();
+        float *outputBuffer = output.getBlock() + ch;
 
         for (int i = 0; i < numFrames; i++) {
             // read one, write into the proper interleaved output channel
@@ -47,6 +48,6 @@
             outputBuffer += channelCount; // advance to next multichannel frame
         }
     }
-    return AUDIO_RESULT_SUCCESS;
+    return numFrames;
 }
 
diff --git a/apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.h b/apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.h
similarity index 67%
rename from apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.h
rename to apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.h
index 3cdf376..d2f39a3 100644
--- a/apps/OboeTester/app/src/main/cpp/ManyToMultiConverter.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/ManyToMultiConverter.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef OBOETESTER_MANY_TO_MULTI_CONVERTER_H
-#define OBOETESTER_MANY_TO_MULTI_CONVERTER_H
+#ifndef FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
+#define FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
 
 #include <unistd.h>
 #include <sys/types.h>
@@ -24,24 +24,24 @@
 #include "AudioProcessorBase.h"
 
 /**
- * Combine multiple mono inputs into one multi-channel output.
+ * Combine multiple mono inputs into one interleaved multi-channel output.
  */
-class ManyToMultiConverter : public AudioProcessorBase {
+class ManyToMultiConverter : public flowgraph::AudioProcessorBase {
 public:
     explicit ManyToMultiConverter(int32_t channelCount);
 
     virtual ~ManyToMultiConverter() = default;
 
-    AudioResult onProcess(
-            uint64_t framePosition,
+    int32_t onProcess(
+            int64_t framePosition,
             int numFrames) override;
 
     void setEnabled(bool enabled) {};
 
-    std::vector<std::unique_ptr<AudioInputPort> > inputs;
-    AudioOutputPort output;
+    std::vector<std::unique_ptr<flowgraph::AudioFloatInputPort>> inputs;
+    flowgraph::AudioFloatOutputPort output;
 
 private:
 };
 
-#endif //OBOETESTER_MANY_TO_MULTI_CONVERTER_H
+#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
diff --git a/apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.cpp
similarity index 65%
rename from apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.cpp
rename to apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.cpp
index 53746b4..78aad52 100644
--- a/apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.cpp
@@ -14,31 +14,34 @@
  * limitations under the License.
  */
 
+
 #include <unistd.h>
 #include "AudioProcessorBase.h"
 #include "MonoToMultiConverter.h"
 
+using namespace flowgraph;
+
 MonoToMultiConverter::MonoToMultiConverter(int32_t channelCount)
         : input(*this, 1)
         , output(*this, channelCount) {
 }
 
-AudioResult MonoToMultiConverter::onProcess(
-        uint64_t framePosition,
-        int numFrames) {
-    input.pullData(framePosition, numFrames);
+MonoToMultiConverter::~MonoToMultiConverter() { }
 
-    const float *inputBuffer = input.getFloatBuffer(numFrames);
-    float *outputBuffer = output.getFloatBuffer(numFrames);
+int32_t MonoToMultiConverter::onProcess(int64_t framePosition, int32_t numFrames) {
+    int32_t framesToProcess = input.pullData(framePosition, numFrames);
+
+    const float *inputBuffer = input.getBlock();
+    float *outputBuffer = output.getBlock();
     int32_t channelCount = output.getSamplesPerFrame();
-
-    for (int i = 0; i < numFrames; i++) {
+    // TODO maybe move to audio_util as audio_mono_to_multi()
+    for (int i = 0; i < framesToProcess; i++) {
         // read one, write many
         float sample = *inputBuffer++;
-        for (int ch = 0; ch < channelCount; ch++) {
+        for (int channel = 0; channel < channelCount; channel++) {
             *outputBuffer++ = sample;
         }
     }
-    return AUDIO_RESULT_SUCCESS;
+    return framesToProcess;
 }
 
diff --git a/apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.h b/apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.h
similarity index 60%
rename from apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.h
rename to apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.h
index 9132fa1..0a1eb4f 100644
--- a/apps/OboeTester/app/src/main/cpp/MonoToMultiConverter.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/MonoToMultiConverter.h
@@ -14,33 +14,32 @@
  * limitations under the License.
  */
 
-#ifndef OBOETESTER_MONO_TO_MULTI_CONVERTER_H
-#define OBOETESTER_MONO_TO_MULTI_CONVERTER_H
+#ifndef FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
+#define FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
 
+namespace flowgraph {
+
 /**
- * Duplicate a single mono input across multiple channels of output.
+ * Convert a monophonic stream to a multi-channel stream
+ * with the same signal on each channel.
  */
-class MonoToMultiConverter : AudioProcessorBase {
+class MonoToMultiConverter : public AudioProcessorBase {
 public:
     explicit MonoToMultiConverter(int32_t channelCount);
 
-    virtual ~MonoToMultiConverter() = default;
+    virtual ~MonoToMultiConverter();
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames) override;
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
 
-    void setEnabled(bool enabled) {};
-
-    AudioInputPort input;
-    AudioOutputPort output;
-
-private:
+    AudioFloatInputPort input;
+    AudioFloatOutputPort output;
 };
 
-#endif //OBOETESTER_MONO_TO_MULTI_CONVERTER_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
diff --git a/apps/OboeTester/app/src/main/cpp/OscillatorBase.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.cpp
similarity index 96%
rename from apps/OboeTester/app/src/main/cpp/OscillatorBase.cpp
rename to apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.cpp
index f839f78..361f4db 100644
--- a/apps/OboeTester/app/src/main/cpp/OscillatorBase.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.cpp
@@ -16,6 +16,8 @@
 
 #include "OscillatorBase.h"
 
+using namespace flowgraph;
+
 OscillatorBase::OscillatorBase()
         : frequency(*this, 1)
         , amplitude(*this, 1)
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.h b/apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.h
new file mode 100644
index 0000000..b3144bb
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/OscillatorBase.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018 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 NATIVEOBOE_OSCILLATORBASE_H
+#define NATIVEOBOE_OSCILLATORBASE_H
+
+#include "AudioProcessorBase.h"
+
+/**
+ * Base class for various oscillators.
+ * The oscillator has a phase that ranges from -1.0 to +1.0.
+ * That makes it easier to implement simple algebraic waveforms.
+ *
+ * Subclasses must implement onProcess().
+ *
+ * This module has "frequency" and "amplitude" ports for control.
+ */
+
+class OscillatorBase : public flowgraph::AudioProcessorBase {
+public:
+    OscillatorBase();
+
+    virtual ~OscillatorBase() = default;
+
+    void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        mFrequencyToPhaseIncrement = 1.0f / sampleRate; // scaler
+    }
+
+    float getSampleRate() {
+        return mSampleRate;
+    }
+
+    /**
+     * Control the frequency of the oscillator in Hz.
+     */
+    flowgraph::AudioFloatInputPort  frequency;
+
+    /**
+     * Control the linear amplitude of the oscillator.
+     * Silence is 0.0.
+     * A typical full amplitude would be 1.0.
+     */
+    flowgraph::AudioFloatInputPort  amplitude;
+
+    flowgraph::AudioFloatOutputPort output;
+
+protected:
+    /**
+     * Increment phase based on frequency in Hz.
+     * Frequency may be positive or negative.
+     *
+     * Frequency should not exceed +/- Nyquist Rate.
+     * Nyquist Rate is sampleRate/2.
+     */
+    float incrementPhase(float frequency) {
+        mPhase += frequency * mFrequencyToPhaseIncrement;
+        // Wrap phase in the range of -1 to +1
+        if (mPhase >= 1.0f) {
+            mPhase -= 2.0f;
+        } else if (mPhase < -1.0f) {
+            mPhase += 2.0f;
+        }
+        return mPhase;
+    }
+
+    float   mPhase = 0.0;  // phase that ranges from -1.0 to +1.0
+    float   mSampleRate = 0.0f;
+    float   mFrequencyToPhaseIncrement = 0.0f; // scaler for converting frequency to phase increment
+};
+
+
+#endif //NATIVEOBOE_OSCILLATORBASE_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.cpp
new file mode 100644
index 0000000..acd6fa4
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 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 <algorithm>
+#include <unistd.h>
+#include "AudioProcessorBase.h"
+#include "RampLinear.h"
+
+using namespace flowgraph;
+
+RampLinear::RampLinear(int32_t channelCount)
+        : input(*this, channelCount)
+        , output(*this, channelCount) {
+    mTarget.store(1.0f);
+}
+
+void RampLinear::setLengthInFrames(int32_t frames) {
+    mLengthInFrames = frames;
+}
+
+void RampLinear::setTarget(float target) {
+    mTarget.store(target);
+}
+
+float RampLinear::interpolateCurrent() {
+    return mLevelTo - (mRemaining * mScaler);
+}
+
+int32_t RampLinear::onProcess(int64_t framePosition, int32_t numFrames) {
+    int32_t framesToProcess = input.pullData(framePosition, numFrames);
+    const float *inputBuffer = input.getBlock();
+    float *outputBuffer = output.getBlock();
+    int32_t channelCount = output.getSamplesPerFrame();
+
+    float target = getTarget();
+    if (target != mLevelTo) {
+        // Start new ramp. Continue from previous level.
+        mLevelFrom = interpolateCurrent();
+        mLevelTo = target;
+        mRemaining = mLengthInFrames;
+        mScaler = (mLevelTo - mLevelFrom) / mLengthInFrames; // for interpolation
+    }
+
+    int32_t framesLeft = framesToProcess;
+
+    if (mRemaining > 0) { // Ramping? This doesn't happen very often.
+        int32_t framesToRamp = std::min(framesLeft, mRemaining);
+        framesLeft -= framesToRamp;
+        while (framesToRamp > 0) {
+            float currentLevel = interpolateCurrent();
+            for (int ch = 0; ch < channelCount; ch++) {
+                *outputBuffer++ = *inputBuffer++ * currentLevel;
+            }
+            mRemaining--;
+            framesToRamp--;
+        }
+    }
+
+    // Process any frames after the ramp.
+    int32_t samplesLeft = framesLeft * channelCount;
+    for (int i = 0; i < samplesLeft; i++) {
+        *outputBuffer++ = *inputBuffer++ * mLevelTo;
+    }
+
+    return framesToProcess;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.h b/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.h
new file mode 100644
index 0000000..0d5ed10
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/RampLinear.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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_RAMP_LINEAR_H
+#define FLOWGRAPH_RAMP_LINEAR_H
+
+#include <atomic>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "AudioProcessorBase.h"
+
+namespace flowgraph {
+
+/**
+ * When the target is modified then the output will ramp smoothly
+ * between the original and the new target value.
+ * This can be used to smooth out control values and reduce pops.
+ *
+ * The target may be updated while a ramp is in progress, which will trigger
+ * a new ramp from the current value.
+ */
+class RampLinear : public AudioProcessorBase {
+public:
+    explicit RampLinear(int32_t channelCount);
+
+    virtual ~RampLinear() = default;
+
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
+
+    /**
+     * This is used for the next ramp.
+     * Calling this does not affect a ramp that is in progress.
+     */
+    void setLengthInFrames(int32_t frames);
+
+    int32_t getLengthInFrames() const {
+        return mLengthInFrames;
+    }
+
+    /**
+     * This may be safely called by another thread.
+     * @param target
+     */
+    void setTarget(float target);
+
+    float getTarget() const {
+        return mTarget.load();
+    }
+
+    /**
+     * Force the nextSegment to start from this level.
+     *
+     * WARNING: this can cause a discontinuity if called while the ramp is being used.
+     * Only call this when setting the initial ramp.
+     *
+     * @param level
+     */
+    void forceCurrent(float level) {
+        mLevelFrom = level;
+        mLevelTo = level;
+    }
+
+    AudioFloatInputPort input;
+    AudioFloatOutputPort output;
+
+private:
+
+    float interpolateCurrent();
+
+    std::atomic<float>  mTarget;
+
+    int32_t             mLengthInFrames  = 48000.0f / 100.0f ; // 10 msec at 48000 Hz;
+    int32_t             mRemaining       = 0;
+    float               mScaler          = 0.0f;
+    float               mLevelFrom       = 0.0f;
+    float               mLevelTo         = 0.0f;
+};
+
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_RAMP_LINEAR_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.cpp
new file mode 100644
index 0000000..149e489
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 <math.h>
+#include <unistd.h>
+
+#include "SawtoothOscillator.h"
+
+SawtoothOscillator::SawtoothOscillator()
+        : OscillatorBase() {
+}
+
+int32_t SawtoothOscillator::onProcess(
+        int64_t framePosition,
+        int numFrames) {
+
+    frequency.pullData(framePosition, numFrames);
+    amplitude.pullData(framePosition, numFrames);
+
+    const float *frequencies = frequency.getBlock();
+    const float *amplitudes = amplitude.getBlock();
+    float *buffer = output.getBlock();
+
+    // Use the phase directly as a non-band-limited "sawtooth".
+    // This will generate unpleasant aliasing artifacts at higher frequencies.
+    for (int i = 0; i < numFrames; i++) {
+        float phase = incrementPhase(frequencies[i]); // phase ranges from -1 to +1
+        *buffer++ = phase * amplitudes[i];
+    }
+
+    return numFrames;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/SineGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.h
similarity index 61%
copy from apps/OboeTester/app/src/main/cpp/SineGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.h
index ca0f988..6d444ae 100644
--- a/apps/OboeTester/app/src/main/cpp/SineGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SawtoothOscillator.h
@@ -14,21 +14,25 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_SINEGENERATOR_H
-#define NATIVEOBOE_SINEGENERATOR_H
+#ifndef FLOWGRAPH_SAWTOOTH_OSCILLATOR_H
+#define FLOWGRAPH_SAWTOOTH_OSCILLATOR_H
 
 #include <unistd.h>
 
 #include "OscillatorBase.h"
 
-class SineGenerator : public OscillatorBase {
+/**
+ * Oscillator that generates a sawtooth wave at the specified frequency and amplitude.
+ *
+ * The waveform is not band-limited so it will have aliasing artifacts at higher frequencies.
+ */
+class SawtoothOscillator : public OscillatorBase {
 public:
-    SineGenerator();
+    SawtoothOscillator();
 
-    AudioResult onProcess(
-            uint64_t framePosition,
+    int32_t onProcess(
+            int64_t framePosition,
             int numFrames) override;
 };
 
-
-#endif //NATIVEOBOE_SINEGENERATOR_H
+#endif //FLOWGRAPH_SAWTOOTH_OSCILLATOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/SineGenerator.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.cpp
similarity index 61%
rename from apps/OboeTester/app/src/main/cpp/SineGenerator.cpp
rename to apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.cpp
index 58c55c5..7657b0c 100644
--- a/apps/OboeTester/app/src/main/cpp/SineGenerator.cpp
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.cpp
@@ -17,28 +17,32 @@
 #include <math.h>
 #include <unistd.h>
 
-#include "SineGenerator.h"
+#include "SineOscillator.h"
 
-SineGenerator::SineGenerator()
+/*
+ * This calls sinf() so it is not very efficient.
+ * A more efficient implementation might use a wave-table or a polynomial.
+ */
+SineOscillator::SineOscillator()
         : OscillatorBase() {
 }
 
-AudioResult SineGenerator::onProcess(
-        uint64_t framePosition,
+int32_t SineOscillator::onProcess(
+        int64_t framePosition,
         int numFrames) {
 
     frequency.pullData(framePosition, numFrames);
     amplitude.pullData(framePosition, numFrames);
 
-    const float *frequencies = frequency.getFloatBuffer(numFrames);
-    const float *amplitudes = amplitude.getFloatBuffer(numFrames);
-    float *buffer = output.getFloatBuffer(numFrames);
+    const float *frequencies = frequency.getBlock();
+    const float *amplitudes = amplitude.getBlock();
+    float *buffer = output.getBlock();
 
     // Generate sine wave.
     for (int i = 0; i < numFrames; i++) {
-        float phase = incrementPhase(frequencies[i]);
-        *buffer++ = sinf(phase) * amplitudes[i];
+        float phase = incrementPhase(frequencies[i]); // phase ranges from -1 to +1
+        *buffer++ = sinf(phase * M_PI) * amplitudes[i];
     }
 
-    return AUDIO_RESULT_SUCCESS;
+    return numFrames;
 }
diff --git a/apps/OboeTester/app/src/main/cpp/SineGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.h
similarity index 68%
rename from apps/OboeTester/app/src/main/cpp/SineGenerator.h
rename to apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.h
index ca0f988..a7ab8a7 100644
--- a/apps/OboeTester/app/src/main/cpp/SineGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SineOscillator.h
@@ -14,21 +14,23 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_SINEGENERATOR_H
-#define NATIVEOBOE_SINEGENERATOR_H
+#ifndef FLOWGRAPH_SINE_OSCILLATOR_H
+#define FLOWGRAPH_SINE_OSCILLATOR_H
 
 #include <unistd.h>
 
 #include "OscillatorBase.h"
 
-class SineGenerator : public OscillatorBase {
+/**
+ * Oscillator that generates a sine wave at the specified frequency and amplitude.
+ */
+class SineOscillator : public OscillatorBase {
 public:
-    SineGenerator();
+    SineOscillator();
 
-    AudioResult onProcess(
-            uint64_t framePosition,
+    int32_t onProcess(
+            int64_t framePosition,
             int numFrames) override;
 };
 
-
-#endif //NATIVEOBOE_SINEGENERATOR_H
+#endif //FLOWGRAPH_SINE_OSCILLATOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.cpp
new file mode 100644
index 0000000..fb3dcbc
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+#include "AudioProcessorBase.h"
+#include "SinkFloat.h"
+
+using namespace flowgraph;
+
+SinkFloat::SinkFloat(int32_t channelCount)
+        : AudioSink(channelCount) {
+}
+
+int32_t SinkFloat::read(void *data, int32_t numFrames) {
+    float *floatData = (float *) data;
+    int32_t channelCount = input.getSamplesPerFrame();
+
+    int32_t framesLeft = numFrames;
+    while (framesLeft > 0) {
+        // Run the graph and pull data through the input port.
+        int32_t framesRead = pull(framesLeft);
+        if (framesRead <= 0) {
+            break;
+        }
+        const float *signal = input.getBlock();
+        int32_t numSamples = framesRead * channelCount;
+        memcpy(floatData, signal, numSamples * sizeof(float));
+        floatData += numSamples;
+        framesLeft -= framesRead;
+    }
+    return numFrames - framesLeft;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.h
similarity index 66%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.h
index 5db11b5..be3fc88 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkFloat.h
@@ -14,25 +14,28 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+
+#ifndef FLOWGRAPH_SINK_FLOAT_H
+#define FLOWGRAPH_SINK_FLOAT_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+
+/**
+ * AudioSink that lets you read data as 32-bit floats.
+ */
+class SinkFloat : public AudioSink {
 public:
-    ImpulseGenerator();
+    explicit SinkFloat(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
+    int32_t read(void *data, int32_t numFrames) override;
 
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SINK_FLOAT_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.cpp
new file mode 100644
index 0000000..1e66cbd
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+
+#include "SinkI16.h"
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+#include <audio_utils/primitives.h>
+#endif
+
+using namespace flowgraph;
+
+SinkI16::SinkI16(int32_t channelCount)
+        : AudioSink(channelCount) {}
+
+int32_t SinkI16::read(void *data, int32_t numFrames) {
+    int16_t *shortData = (int16_t *) data;
+    const int32_t channelCount = input.getSamplesPerFrame();
+
+    int32_t framesLeft = numFrames;
+    while (framesLeft > 0) {
+        // Run the graph and pull data through the input port.
+        int32_t framesRead = pull(framesLeft);
+        if (framesRead <= 0) {
+            break;
+        }
+        const float *signal = input.getBlock();
+        int32_t numSamples = framesRead * channelCount;
+#if FLOWGRAPH_ANDROID_INTERNAL
+        memcpy_to_i16_from_float(shortData, signal, numSamples);
+        shortData += numSamples;
+        signal += numSamples;
+#else
+        for (int i = 0; i < numSamples; i++) {
+            int32_t n = (int32_t) (*signal++ * 32768.0f);
+            *shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
+        }
+#endif
+        framesLeft -= framesRead;
+    }
+    return numFrames - framesLeft;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.h
similarity index 66%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.h
index 5db11b5..f861544 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI16.h
@@ -14,25 +14,26 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+#ifndef FLOWGRAPH_SINK_I16_H
+#define FLOWGRAPH_SINK_I16_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+
+/**
+ * AudioSink that lets you read data as 16-bit signed integers.
+ */
+class SinkI16 : public AudioSink {
 public:
-    ImpulseGenerator();
+    explicit SinkI16(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t read(void *data, int32_t numFrames) override;
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SINK_I16_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.cpp
new file mode 100644
index 0000000..fa8c68d
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+
+
+#include "AudioProcessorBase.h"
+#include "SinkI24.h"
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+#include <audio_utils/primitives.h>
+#endif
+
+using namespace flowgraph;
+
+SinkI24::SinkI24(int32_t channelCount)
+        : AudioSink(channelCount) {}
+
+int32_t SinkI24::read(void *data, int32_t numFrames) {
+    uint8_t *byteData = (uint8_t *) data;
+    const int32_t channelCount = input.getSamplesPerFrame();
+
+    int32_t framesLeft = numFrames;
+    while (framesLeft > 0) {
+        // Run the graph and pull data through the input port.
+        int32_t framesRead = pull(framesLeft);
+        if (framesRead <= 0) {
+            break;
+        }
+        const float *floatData = input.getBlock();
+        int32_t numSamples = framesRead * channelCount;
+#if FLOWGRAPH_ANDROID_INTERNAL
+        memcpy_to_p24_from_float(byteData, floatData, numSamples);
+        static const int kBytesPerI24Packed = 3;
+        byteData += numSamples * kBytesPerI24Packed;
+        floatData += numSamples;
+#else
+        const int32_t kI24PackedMax = 0x007FFFFF;
+        const int32_t kI24PackedMin = 0xFF800000;
+        for (int i = 0; i < numSamples; i++) {
+            int32_t n = (int32_t) (*floatData++ * 0x00800000);
+            n = std::min(kI24PackedMax, std::max(kI24PackedMin, n)); // clip
+            // Write as a packed 24-bit integer in Little Endian format.
+            *byteData++ = (uint8_t) n;
+            *byteData++ = (uint8_t) (n >> 8);
+            *byteData++ = (uint8_t) (n >> 16);
+        }
+#endif
+        framesLeft -= framesRead;
+    }
+    return numFrames - framesLeft;
+}
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.h
similarity index 64%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.h
index 5db11b5..682b16f 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SinkI24.h
@@ -14,25 +14,27 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+#ifndef FLOWGRAPH_SINK_I24_H
+#define FLOWGRAPH_SINK_I24_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+
+/**
+ * AudioSink that lets you read data as packed 24-bit signed integers.
+ * The sample size is 3 bytes.
+ */
+class SinkI24 : public AudioSink {
 public:
-    ImpulseGenerator();
+    explicit SinkI24(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t read(void *data, int32_t numFrames) override;
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SINK_I24_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.cpp
new file mode 100644
index 0000000..4bb674f
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+#include "AudioProcessorBase.h"
+#include "SourceFloat.h"
+
+using namespace flowgraph;
+
+SourceFloat::SourceFloat(int32_t channelCount)
+        : AudioSource(channelCount) {
+}
+
+int32_t SourceFloat::onProcess(int64_t framePosition, int32_t numFrames) {
+
+    float *outputBuffer = output.getBlock();
+    int32_t channelCount = output.getSamplesPerFrame();
+
+    int32_t framesLeft = mSizeInFrames - mFrameIndex;
+    int32_t framesToProcess = std::min(numFrames, framesLeft);
+    int32_t numSamples = framesToProcess * channelCount;
+
+    const float *floatBase = (float *) mData;
+    const float *floatData = &floatBase[mFrameIndex * channelCount];
+    memcpy(outputBuffer, floatData, numSamples * sizeof(float));
+    mFrameIndex += framesToProcess;
+    return framesToProcess;
+}
+
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.h
similarity index 64%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.h
index 5db11b5..efede25 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceFloat.h
@@ -14,25 +14,26 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+#ifndef FLOWGRAPH_SOURCE_FLOAT_H
+#define FLOWGRAPH_SOURCE_FLOAT_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+
+/**
+ * AudioSource that reads a block of pre-defined float data.
+ */
+class SourceFloat : public AudioSource {
 public:
-    ImpulseGenerator();
+    explicit SourceFloat(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SOURCE_FLOAT_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.cpp
new file mode 100644
index 0000000..f172968
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+
+#include "AudioProcessorBase.h"
+#include "SourceI16.h"
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+#include <audio_utils/primitives.h>
+#endif
+
+using namespace flowgraph;
+
+SourceI16::SourceI16(int32_t channelCount)
+        : AudioSource(channelCount) {
+}
+
+int32_t SourceI16::onProcess(int64_t framePosition, int32_t numFrames) {
+    float *floatData = output.getBlock();
+    int32_t channelCount = output.getSamplesPerFrame();
+
+    int32_t framesLeft = mSizeInFrames - mFrameIndex;
+    int32_t framesToProcess = std::min(numFrames, framesLeft);
+    int32_t numSamples = framesToProcess * channelCount;
+
+    const int16_t *shortBase = static_cast<const int16_t *>(mData);
+    const int16_t *shortData = &shortBase[mFrameIndex * channelCount];
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+    memcpy_to_float_from_i16(floatData, shortData, numSamples);
+#else
+    for (int i = 0; i < numSamples; i++) {
+        *floatData++ = *shortData++ * (1.0f / 32768);
+    }
+#endif
+
+    mFrameIndex += framesToProcess;
+    return framesToProcess;
+}
\ No newline at end of file
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.h
similarity index 64%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.h
index 5db11b5..9481e0b 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI16.h
@@ -14,25 +14,25 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+#ifndef FLOWGRAPH_SOURCE_I16_H
+#define FLOWGRAPH_SOURCE_I16_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+/**
+ * AudioSource that reads a block of pre-defined 16-bit integer data.
+ */
+class SourceI16 : public AudioSource {
 public:
-    ImpulseGenerator();
+    explicit SourceI16(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SOURCE_I16_H
diff --git a/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.cpp b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.cpp
new file mode 100644
index 0000000..fee1ce4
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 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 <algorithm>
+#include <unistd.h>
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+#include <audio_utils/primitives.h>
+#endif
+
+#include "AudioProcessorBase.h"
+#include "SourceI24.h"
+
+using namespace flowgraph;
+
+constexpr int kBytesPerI24Packed = 3;
+
+SourceI24::SourceI24(int32_t channelCount)
+        : AudioSource(channelCount) {
+}
+
+int32_t SourceI24::onProcess(int64_t framePosition, int32_t numFrames) {
+    float *floatData = output.getBlock();
+    int32_t channelCount = output.getSamplesPerFrame();
+
+    int32_t framesLeft = mSizeInFrames - mFrameIndex;
+    int32_t framesToProcess = std::min(numFrames, framesLeft);
+    int32_t numSamples = framesToProcess * channelCount;
+
+    const uint8_t *byteBase = (uint8_t *) mData;
+    const uint8_t *byteData = &byteBase[mFrameIndex * channelCount * kBytesPerI24Packed];
+
+#if FLOWGRAPH_ANDROID_INTERNAL
+    memcpy_to_float_from_p24(floatData, byteData, numSamples);
+#else
+    static const float scale = 1. / (float)(1UL << 31);
+    for (int i = 0; i < numSamples; i++) {
+        // Assemble the data assuming Little Endian format.
+        int32_t pad = byteData[2];
+        pad <<= 8;
+        pad |= byteData[1];
+        pad <<= 8;
+        pad |= byteData[0];
+        pad <<= 8; // Shift to 32 bit data so the sign is correct.
+        byteData += kBytesPerI24Packed;
+        *floatData++ = pad * scale; // scale to range -1.0 to 1.0
+    }
+#endif
+
+    mFrameIndex += framesToProcess;
+    return framesToProcess;
+}
\ No newline at end of file
diff --git a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.h
similarity index 64%
copy from apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
copy to apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.h
index 5db11b5..c76b82d 100644
--- a/apps/OboeTester/app/src/main/cpp/ImpulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/flowgraph/SourceI24.h
@@ -14,25 +14,26 @@
  * limitations under the License.
  */
 
-#ifndef NATIVEOBOE_IMPULSE_GENERATOR_H
-#define NATIVEOBOE_IMPULSE_GENERATOR_H
+#ifndef FLOWGRAPH_SOURCE_I24_H
+#define FLOWGRAPH_SOURCE_I24_H
 
 #include <unistd.h>
 #include <sys/types.h>
 
 #include "AudioProcessorBase.h"
-#include "OscillatorBase.h"
 
-class ImpulseGenerator : public OscillatorBase {
+namespace flowgraph {
+
+/**
+ * AudioSource that reads a block of pre-defined 24-bit packed integer data.
+ */
+class SourceI24 : public AudioSource {
 public:
-    ImpulseGenerator();
+    explicit SourceI24(int32_t channelCount);
 
-    AudioResult onProcess(
-            uint64_t framePosition,
-            int numFrames);
-
-
-
+    int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
 };
 
-#endif //NATIVEOBOE_IMPULSE_GENERATOR_H
+} /* namespace flowgraph */
+
+#endif //FLOWGRAPH_SOURCE_I24_H
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
index 2d49ae8..936aff2 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
@@ -36,7 +36,7 @@
         Log.i(TapToToneActivity.TAG, "create OboeAudioOutputStream ---------");
         mOboeAudioOutputStream = new OboeAudioOutputStream();
         mCurrentAudioStream = mOboeAudioOutputStream;
-        setToneType(OboeAudioOutputStream.TONE_TYPE_SINE_STEADY);
+        setToneType(OboeAudioOutputStream.TONE_TYPE_SINE);
         setEnabled(false);
     }
 
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioOutputStream.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioOutputStream.java
index 763e4c3..da33822 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioOutputStream.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioOutputStream.java
@@ -23,8 +23,9 @@
 
     // WARNING - must match order in strings.xml
     public static final int TONE_TYPE_SAW_PING = 0;
-    public static final int TONE_TYPE_SINE_STEADY = 1;
+    public static final int TONE_TYPE_SINE = 1;
     public static final int TONE_TYPE_IMPULSE = 2;
+    public static final int TONE_TYPE_SAWTOOTH = 3;
 
     @Override
     public boolean isInput() {
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivity.java
index f0e5f64..342a429 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivity.java
@@ -20,8 +20,6 @@
 import android.view.View;
 import android.widget.CheckBox;
 
-import com.google.sample.oboe.manualtest.R;
-
 /**
  * Base class for output test activities
  */
@@ -68,7 +66,7 @@
 
     public void startAudio() {
         super.startAudio();
-        mAudioOutTester.setToneType(OboeAudioOutputStream.TONE_TYPE_SINE_STEADY);
+        mAudioOutTester.setToneType(OboeAudioOutputStream.TONE_TYPE_SINE);
         mAudioOutTester.setEnabled(true);
     }