DrumThumper: hack latency buttons

To demo latency at Media Summit
diff --git a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
index 44f546f..698ab5d 100644
--- a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
+++ b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
@@ -135,6 +135,8 @@
  */
 JNIEXPORT void JNICALL Java_com_plausiblesoftware_drumthumper_DrumPlayer_restartStream(JNIEnv*, jobject) {
     sDTPlayer.resetAll();
+    sDTPlayer.teardownAudioStream();
+    usleep(40 * 1000); // give samples time to stop
     if (sDTPlayer.openStream() && sDTPlayer.startStream()){
         __android_log_print(ANDROID_LOG_INFO, TAG, "openStream successful");
     } else {
@@ -162,6 +164,18 @@
     return sDTPlayer.getGain(index);
 }
 
+JNIEXPORT void JNICALL
+Java_com_plausiblesoftware_drumthumper_DrumPlayer_setLatencyMode(JNIEnv *env, jobject thiz,
+                                                                 jint latencyMode) {
+    const int LATENCY_LOW_MMAP = 0; // Must match definitions in DrumPlayer.java
+    const int LATENCY_NORMAL = 2;
+    sDTPlayer.setMMapEnabled(latencyMode == LATENCY_LOW_MMAP);
+    sDTPlayer.setPerformanceMode(
+            (latencyMode == LATENCY_NORMAL)
+            ? oboe::PerformanceMode::None
+            : oboe::PerformanceMode::LowLatency);
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumPlayer.kt b/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumPlayer.kt
index c47f674..b3e1f2c 100644
--- a/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumPlayer.kt
+++ b/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumPlayer.kt
@@ -45,6 +45,10 @@
         val PAN_HIHATOPEN: Float = -1.0f     // Hard Left
         val PAN_HIHATCLOSED: Float = -1.0f   // Hard Left
 
+        val LATENCY_LOW_MMAP = 0
+        val LATENCY_LOW_LEGACY = 1
+        val LATENCY_NORMAL = 2
+
         // Logging Tag
         val TAG: String = "DrumPlayer"
     }
@@ -111,4 +115,6 @@
     external fun clearOutputReset()
 
     external fun restartStream()
+
+    external fun setLatencyMode(latncyMode: Int)
 }
diff --git a/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumThumperActivity.kt b/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumThumperActivity.kt
index ffb3030..65d7d2c 100644
--- a/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumThumperActivity.kt
+++ b/samples/drumthumper/src/main/kotlin/com/plausibleaudio/drumthumper/DrumThumperActivity.kt
@@ -24,6 +24,7 @@
 import android.view.View
 import android.widget.Button
 import android.widget.LinearLayout
+import android.widget.RadioButton
 import android.widget.SeekBar
 import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
@@ -159,13 +160,43 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        // UI
+        setContentView(R.layout.drumthumper_activity)
+
         mAudioMgr = getSystemService(Context.AUDIO_SERVICE) as AudioManager
 
+        val mRadioLatency0 = findViewById<View>(R.id.latencyMode0) as RadioButton
+        mRadioLatency0.setOnClickListener(View.OnClickListener { view ->
+            onLatencyRadioButtonClicked(view, 0)
+        })
+
+        val mRadioLatency1 = findViewById<View>(R.id.latencyMode1) as RadioButton?
+        if (mRadioLatency1 != null) {
+            mRadioLatency1.setOnClickListener(View.OnClickListener { view ->
+                onLatencyRadioButtonClicked(view, 1)
+            })
+        }
+
+        val mRadioLatency2 = findViewById<View>(R.id.latencyMode2) as RadioButton?
+        if (mRadioLatency2 != null) {
+            mRadioLatency2.setOnClickListener(View.OnClickListener { view ->
+                onLatencyRadioButtonClicked(view, 2)
+            })
+        }
+    }
+
+    private fun onLatencyRadioButtonClicked(view: View, mode: Int) {
+        val checked = (view as RadioButton).isChecked
+        if (!checked) return
+        mDrumPlayer.setLatencyMode(mode)
+        mDrumPlayer.restartStream()
     }
 
     override fun onStart() {
         super.onStart()
 
+        mDrumPlayer.setLatencyMode(DrumPlayer.LATENCY_LOW_MMAP)
+
         mDrumPlayer.setupAudioStream()
 
         mDrumPlayer.loadWavAssets(getAssets())
@@ -180,9 +211,6 @@
     override fun onResume() {
         super.onResume()
 
-        // UI
-        setContentView(R.layout.drumthumper_activity)
-
         // "Kick" drum
         findViewById<TriggerPad>(R.id.kickPad).addListener(this)
         connectMixSliders(R.id.kickPan, R.id.kickGain, DrumPlayer.BASSDRUM)
diff --git a/samples/drumthumper/src/main/res/layout-land/drumthumper_activity.xml b/samples/drumthumper/src/main/res/layout-land/drumthumper_activity.xml
index b657738..7e934b5 100644
--- a/samples/drumthumper/src/main/res/layout-land/drumthumper_activity.xml
+++ b/samples/drumthumper/src/main/res/layout-land/drumthumper_activity.xml
@@ -491,9 +491,54 @@
             </LinearLayout>
         </LinearLayout>
     </LinearLayout>
-    <Button
+
+    <LinearLayout
+        android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Hide/Show Mix Controls"
-        android:id="@+id/mixCtrlBtn"/>
+        android:layout_height="match_parent">
+
+        <Button
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Hide/Show Mix Controls"
+            android:id="@+id/mixCtrlBtn"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20dp"
+            android:text="Latency:" />
+
+        <RadioGroup
+            android:id="@+id/latencyModeGroup"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <RadioButton
+                android:id="@+id/latencyMode0"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:checked="true"
+                android:textSize="20sp"
+                android:text="MMAP"
+                />
+
+            <RadioButton
+                android:id="@+id/latencyMode1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20dp"
+                android:text="Legacy"
+                />
+
+            <RadioButton
+                android:id="@+id/latencyMode2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20dp"
+                android:text="None"
+                />
+        </RadioGroup>
+    </LinearLayout>
 </LinearLayout>
diff --git a/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml b/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
index 1c0573b..cc2b998 100644
--- a/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
+++ b/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
@@ -508,4 +508,49 @@
         android:layout_height="wrap_content"
         android:text="Hide/Show Mix Controls"
         android:id="@+id/mixCtrlBtn"/>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:padding="20dp"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20dp"
+            android:text="Latency:" />
+
+        <RadioGroup
+            android:id="@+id/latencyModeGroup"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <RadioButton
+                android:id="@+id/latencyMode0"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:checked="true"
+                android:textSize="20sp"
+                android:text="MMAP"
+                />
+
+            <RadioButton
+                android:id="@+id/latencyMode1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20dp"
+                android:text="Legacy"
+                />
+
+            <RadioButton
+                android:id="@+id/latencyMode2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textSize="20dp"
+                android:text="None"
+                />
+        </RadioGroup>
+    </LinearLayout>
 </LinearLayout>
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
index 4948d88..22586ee 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
@@ -85,11 +85,14 @@
     // we will resample source data to device rate, so take default sample rate
     builder.setDataCallback(mDataCallback);
     builder.setErrorCallback(mErrorCallback);
-    builder.setPerformanceMode(PerformanceMode::LowLatency);
+    builder.setPerformanceMode(mPerformanceMode);
     builder.setSharingMode(SharingMode::Exclusive);
-    builder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);
+//    builder.setSampleRateConversionQuality(SampleRateConversionQuality::Medium);
 
+    bool wasMMapEnabled = OboeExtensions::isMMapEnabled();
+    OboeExtensions::setMMapEnabled(mMMapEnabled);
     Result result = builder.openStream(mAudioStream);
+    OboeExtensions::setMMapEnabled(wasMMapEnabled);
     if (result != Result::OK){
         __android_log_print(
                 ANDROID_LOG_ERROR,
@@ -119,7 +122,7 @@
     int tryCount = 0;
     while (tryCount < 3) {
         bool wasOpenSuccessful = true;
-        // Assume that apenStream() was called successfully before startStream() call.
+        // Assume that openStream() was called successfully before startStream() call.
         if (tryCount > 0) {
             usleep(20 * 1000); // Sleep between tries to give the system time to settle.
             wasOpenSuccessful = openStream(); // Try to open the stream again after the first try.
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
index b388b8f..82ef106 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
@@ -68,6 +68,22 @@
     void setGain(int index, float gain);
     float getGain(int index);
 
+    oboe::PerformanceMode getPerformanceMode() const {
+        return mPerformanceMode;
+    }
+
+    void setPerformanceMode(oboe::PerformanceMode performanceMode) {
+        mPerformanceMode = performanceMode;
+    }
+
+    bool isMMapEnabled() const {
+        return mMMapEnabled;
+    }
+
+    void setMMapEnabled(bool enabled) {
+        mMMapEnabled = enabled;
+    }
+
 private:
     class MyDataCallback : public oboe::AudioStreamDataCallback {
     public:
@@ -101,6 +117,10 @@
     // Playback Audio attributes
     int32_t mChannelCount;
     int32_t mSampleRate;
+    oboe::PerformanceMode mPerformanceMode = oboe::PerformanceMode::LowLatency;
+
+private:
+    bool mMMapEnabled = false;
 
     // Sample Data
     int32_t mNumSampleBuffers;