Added control of gain for each drum and a mix UI for gain and pan.
diff --git a/samples/build.gradle b/samples/build.gradle
index d522839..d51e478 100644
--- a/samples/build.gradle
+++ b/samples/build.gradle
@@ -27,7 +27,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.5.3'
+        classpath 'com.android.tools.build:gradle:3.6.3'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
diff --git a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
index 1517a78..274036f 100644
--- a/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
+++ b/samples/drumthumper/src/main/cpp/DrumPlayerJNI.cpp
@@ -128,6 +128,27 @@
     }
 }
 
+JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_setPan(
+        JNIEnv *env, jobject thiz, jint index, jfloat pan) {
+    sDTPlayer.setPan(index, pan);
+}
+
+JNIEXPORT jfloat JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_getPan(
+        JNIEnv *env, jobject thiz, jint  index) {
+    // TODO: implement getPan()
+    return sDTPlayer.getPan(index);
+}
+
+JNIEXPORT void JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_setGain(
+        JNIEnv *env, jobject thiz, jint  index, jfloat gain) {
+    sDTPlayer.setGain(index, gain);
+}
+
+JNIEXPORT jfloat JNICALL Java_com_google_oboe_sample_drumthumper_DrumPlayer_getGain(
+        JNIEnv *env, jobject thiz, jint index) {
+    return sDTPlayer.getGain(index);
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
index 2937600..598766c 100644
--- a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
+++ b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumPlayer.kt
@@ -95,6 +95,12 @@
 
     external fun trigger(drumIndex: Int)
 
+    external fun setPan(index: Int, pan: Float)
+    external fun getPan(index: Int): Float
+
+    external fun setGain(index: Int, gain: Float)
+    external fun getGain(index: Int): Float
+
     external fun getOutputReset() : Boolean
     external fun clearOutputReset()
 
diff --git a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
index b8f9811..cebc5cc 100644
--- a/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
+++ b/samples/drumthumper/src/main/java/com/google/oboe/sample/drumthumper/DrumThumperActivity.kt
@@ -21,6 +21,7 @@
 import android.media.AudioManager
 import android.os.Bundle
 import android.util.Log
+import android.widget.SeekBar
 import android.widget.Toast
 
 import androidx.appcompat.app.AppCompatActivity
@@ -30,8 +31,11 @@
 import java.time.LocalDateTime;
 
 import kotlin.concurrent.schedule
+import kotlin.math.roundToInt
 
-class DrumThumperActivity : AppCompatActivity(), TriggerPad.DrumPadTriggerListener {
+class DrumThumperActivity : AppCompatActivity(),
+        TriggerPad.DrumPadTriggerListener,
+        SeekBar.OnSeekBarChangeListener {
     private val TAG = "DrumThumperActivity"
 
     private var mAudioMgr: AudioManager? = null
@@ -99,6 +103,26 @@
         }
     }
 
+    // UI Helpers
+    fun gainPosToGainVal(pos: Int) : Float {
+        // map 0 -> 200 to 0.0f -> 2.0f
+        return pos.toFloat() / 100.0f
+    }
+
+    fun gainValToGainPos(value: Float) : Int {
+        return (value * 100.0f).toInt()
+    }
+
+    fun panPosToPanVal(pos: Int) : Float {
+        // map 0 -> 200 to -1.0f -> 1..0f
+        return (pos.toFloat() - 100.0f) / 100.0f
+    }
+
+    fun panValToPanPos(value: Float) : Int {
+        // map -1.0f -> 1.0f to 0 -> 200
+        return ((value * 200.0f) + 100.0f).toInt()
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -125,46 +149,79 @@
         setContentView(R.layout.drumthumper_activity)
 
         // hookup the UI
-        run {
-            var pad: TriggerPad = findViewById(R.id.kickPad)
-            pad.addListener(this)
-        }
+        var sb : SeekBar;
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.snarePad)
-            pad.addListener(this)
-        }
+        // "Kick" drum
+        (findViewById(R.id.kickPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.kickPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.BASSDRUM)))
+        sb = (findViewById(R.id.kickGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.BASSDRUM)))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.midTomPad)
-            pad.addListener(this)
-        }
+        // Snare drum
+        (findViewById(R.id.snarePad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.snarePan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.SNAREDRUM)))
+        sb = (findViewById(R.id.snareGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.SNAREDRUM)))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.lowTomPad)
-            pad.addListener(this)
-        }
+        // Mid tom
+        (findViewById(R.id.midTomPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.midTomPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.MIDTOM)))
+        sb = (findViewById(R.id.midTomGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain((DrumPlayer.MIDTOM))))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.hihatOpenPad)
-            pad.addListener(this)
-        }
+        // Low tom
+        (findViewById(R.id.lowTomPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.lowTomPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.LOWTOM)))
+        sb = (findViewById(R.id.lowTomGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.LOWTOM)))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.hihatClosedPad)
-            pad.addListener(this)
-        }
+        // Open hihat
+        (findViewById(R.id.hihatOpenPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.hihatOpenPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.HIHATOPEN)))
+        sb = (findViewById(R.id.hihatOpenGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.HIHATOPEN)))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.ridePad)
-            pad.addListener(this)
-        }
+        // Closed hihat
+        (findViewById(R.id.hihatClosedPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.hihatClosedPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.HIHATCLOSED)))
+        sb = (findViewById(R.id.hihatClosedGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.HIHATCLOSED)))
 
-        run {
-            var pad: TriggerPad = findViewById(R.id.crashPad)
-            pad.addListener(this)
-        }
+        // Ride cymbal
+        (findViewById(R.id.ridePad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.ridePan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.RIDECYMBAL)))
+        sb = (findViewById(R.id.rideGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.RIDECYMBAL)))
 
+        // Crash cymbal
+        (findViewById(R.id.crashPad) as TriggerPad).addListener(this)
+        sb = (findViewById(R.id.crashPan) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(panValToPanPos(mDrumPlayer.getPan(DrumPlayer.CRASHCYMBAL)))
+        sb = (findViewById(R.id.crashGain) as SeekBar)
+        sb.setOnSeekBarChangeListener(this)
+        sb.setProgress(gainValToGainPos(mDrumPlayer.getGain(DrumPlayer.CRASHCYMBAL)))
     }
 
     override fun onPause() {
@@ -206,4 +263,52 @@
     override fun triggerUp(pad: TriggerPad) {
         // NOP
     }
+
+    //
+    // SeekBar.OnSeekBarChangeListener
+    //
+    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
+        when (seekBar!!.id) {
+            // BASSDRUM
+            R.id.kickGain -> mDrumPlayer.setGain(DrumPlayer.BASSDRUM, gainPosToGainVal(progress))
+            R.id.kickPan -> mDrumPlayer.setPan(DrumPlayer.BASSDRUM, panPosToPanVal(progress))
+
+            // SNAREDRUM
+            R.id.snareGain -> mDrumPlayer.setGain(DrumPlayer.SNAREDRUM, gainPosToGainVal(progress))
+            R.id.snarePan -> mDrumPlayer.setPan(DrumPlayer.SNAREDRUM, panPosToPanVal(progress))
+
+            // MIDTOM
+            R.id.midTomGain -> mDrumPlayer.setGain(DrumPlayer.MIDTOM, gainPosToGainVal(progress))
+            R.id.midTomPan -> mDrumPlayer.setPan(DrumPlayer.MIDTOM, panPosToPanVal(progress))
+
+            // LOWTOM
+            R.id.lowTomGain -> mDrumPlayer.setGain(DrumPlayer.LOWTOM, gainPosToGainVal(progress))
+            R.id.lowTomPan -> mDrumPlayer.setPan(DrumPlayer.LOWTOM, panPosToPanVal(progress))
+
+            // HIHATOPEN
+            R.id.hihatOpenGain -> mDrumPlayer.setGain(DrumPlayer.HIHATOPEN, gainPosToGainVal(progress))
+            R.id.hihatOpenPan -> mDrumPlayer.setPan(DrumPlayer.HIHATOPEN, panPosToPanVal(progress))
+
+            // HIHATCLOSED
+            R.id.hihatClosedGain -> mDrumPlayer.setGain(DrumPlayer.HIHATCLOSED, gainPosToGainVal(progress))
+            R.id.hihatClosedPan -> mDrumPlayer.setPan(DrumPlayer.HIHATCLOSED, panPosToPanVal(progress))
+
+            // RIDECYMBAL
+            R.id.rideGain -> mDrumPlayer.setGain(DrumPlayer.RIDECYMBAL, gainPosToGainVal(progress))
+            R.id.ridePan -> mDrumPlayer.setPan(DrumPlayer.RIDECYMBAL, panPosToPanVal(progress))
+
+            // CRASHCYMBAL
+            R.id.crashGain -> mDrumPlayer.setGain(DrumPlayer.CRASHCYMBAL, gainPosToGainVal(progress))
+            R.id.crashPan -> mDrumPlayer.setPan(DrumPlayer.CRASHCYMBAL, panPosToPanVal(progress))
+        }
+    }
+
+    override fun onStartTrackingTouch(seekBar: SeekBar?) {
+        // NOP
+    }
+
+    override fun onStopTrackingTouch(seekBar: SeekBar?) {
+        // NOP
+    }
+
 }
diff --git a/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml b/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
index 2ff2392..764aa16 100644
--- a/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
+++ b/samples/drumthumper/src/main/res/layout/drumthumper_activity.xml
@@ -20,21 +20,112 @@
         android:layout_height="128dp"
         android:orientation="horizontal">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/kickPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Kick" />
-
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/snarePad"
+        <LinearLayout
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Snare" />
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
+
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/kickPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Kick" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/kickPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/kickGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
+
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/snarePad"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Snare" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/snarePan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/snareGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+
+            </LinearLayout>
+        </LinearLayout>
     </LinearLayout>
 
     <LinearLayout
@@ -42,21 +133,112 @@
         android:layout_height="128dp"
         android:orientation="horizontal">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/hihatOpenPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Open Hat" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/hihatClosedPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Closed Hat" />
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/hihatOpenPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Open Hat" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/hihatOpenPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/hihatOpenGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
+
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/hihatClosedPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Closed Hat" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/hihatClosedPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/hihatClosedGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
     </LinearLayout>
 
     <LinearLayout
@@ -64,21 +246,111 @@
         android:layout_height="128dp"
         android:orientation="horizontal">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/midTomPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Mid Tom" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/lowTomPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Low Tom" />
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/midTomPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Mid Tom" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/midTomPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/midTomGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
+
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/lowTomPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Low Tom" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/lowTomPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/lowTomGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
     </LinearLayout>
 
     <LinearLayout
@@ -86,22 +358,111 @@
         android:layout_height="128dp"
         android:orientation="horizontal">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/ridePad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Ride" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
 
-        <com.google.oboe.sample.drumthumper.TriggerPad
-            android:id="@+id/crashPad"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:padding="5dp"
-            android:text="Crash" />
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/ridePad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Ride" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/ridePan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/rideGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="128dp"
+            android:layout_weight=".5"
+            android:orientation="vertical">
+
+            <com.google.oboe.sample.drumthumper.TriggerPad
+                android:id="@+id/crashPad"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:padding="5dp"
+                android:text="Crash" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="P:"/>
+
+                <SeekBar
+                    android:id="@+id/crashPan"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="G:"/>
+
+                <SeekBar
+                    android:id="@+id/crashGain"
+                    style="@android:style/Widget.DeviceDefault.SeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="20dp"
+                    android:layout_marginTop="5dp"
+                    android:max="200" />
+            </LinearLayout>
+        </LinearLayout>
     </LinearLayout>
 
-
 </LinearLayout>
diff --git a/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp b/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
index 3828cff..ec1bf11 100644
--- a/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
+++ b/samples/iolib/src/main/cpp/player/OneShotSampleSource.cpp
@@ -31,12 +31,12 @@
     if (numWriteFrames != 0) {
         // Mix in the samples
 
-        // investigate unrolling this loop...
+        // investigate unrolling these loops...
         const float* data  = mSampleBuffer->getSampleData();
         if (numChannels == 1) {
             // MONO output
             for (int32_t frameIndex = 0; frameIndex < numWriteFrames; frameIndex++) {
-                outBuff[frameIndex] += data[mCurFrameIndex++];
+                outBuff[frameIndex] += data[mCurFrameIndex++] * mGain;
             }
         } else if (numChannels == 2) {
             // STEREO output
diff --git a/samples/iolib/src/main/cpp/player/SampleSource.h b/samples/iolib/src/main/cpp/player/SampleSource.h
index bdfa692..3f18a7a 100644
--- a/samples/iolib/src/main/cpp/player/SampleSource.h
+++ b/samples/iolib/src/main/cpp/player/SampleSource.h
@@ -39,7 +39,7 @@
     static constexpr float PAN_CENTER = 0.0f;
 
     SampleSource(SampleBuffer *sampleBuffer, float pan)
-     : mSampleBuffer(sampleBuffer), mCurFrameIndex(0), mIsPlaying(false) {
+     : mSampleBuffer(sampleBuffer), mCurFrameIndex(0), mIsPlaying(false), mGain(1.0f) {
         setPan(pan);
     }
     virtual ~SampleSource() {}
@@ -57,10 +57,20 @@
         } else {
             mPan = pan;
         }
+        calcGainFactors();
+    }
 
-        // useful information: http://www.cs.cmu.edu/~music/icm-online/readings/panlaws/
-        mLeftGain = (mPan * 0.5) + 0.5;
-        mRightGain = 1.0 - mLeftGain;
+    float getPan() {
+        return mPan;
+    }
+
+    void setGain(float gain) {
+        mGain = gain;
+        calcGainFactors();
+    }
+
+    float getGain() {
+        return mGain;
     }
 
 protected:
@@ -73,9 +83,19 @@
     // Logical pan value
     float mPan;
 
-    // precomputed channel gains
+    // precomputed channel gains for pan
     float mLeftGain;
     float mRightGain;
+
+    // Overall gain
+    float mGain;
+
+private:
+    void calcGainFactors() {
+        // useful panning information: http://www.cs.cmu.edu/~music/icm-online/readings/panlaws/
+        mRightGain = ((mPan * 0.5) + 0.5) * mGain;
+        mLeftGain = (1.0 - mRightGain) * mGain;
+    }
 };
 
 } // namespace wavlib
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
index 767ecf5..4313668 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp
@@ -134,7 +134,7 @@
     }
 }
 
-void SimpleMultiPlayer::addSampleSource(OneShotSampleSource* source, SampleBuffer* buffer) {
+void SimpleMultiPlayer::addSampleSource(SampleSource* source, SampleBuffer* buffer) {
     mSampleBuffers.push_back(buffer);
     mSampleSources.push_back(source);
     mNumSampleBuffers++;
@@ -172,4 +172,20 @@
     }
 }
 
+void SimpleMultiPlayer::setPan(int index, float pan) {
+    mSampleSources[index]->setPan(pan);
+}
+
+float SimpleMultiPlayer::getPan(int index) {
+    return mSampleSources[index]->getPan();
+}
+
+void SimpleMultiPlayer::setGain(int index, float gain) {
+    mSampleSources[index]->setGain(gain);
+}
+
+float SimpleMultiPlayer::getGain(int index) {
+    return mSampleSources[index]->getGain();
+}
+
 }
diff --git a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
index b0be62b..455bc57 100644
--- a/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
+++ b/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.h
@@ -53,7 +53,7 @@
      * The indexes associated with each source channel is the order in which they
      * are added.
      */
-    void addSampleSource(OneShotSampleSource* source, SampleBuffer* buffer);
+    void addSampleSource(SampleSource* source, SampleBuffer* buffer);
     /**
      * Deallocates and deletes all added source/buffer (see addSampleSource()).
      */
@@ -67,6 +67,12 @@
     bool getOutputReset() { return mOutputReset; }
     void clearOutputReset() { mOutputReset = false; }
 
+    void setPan(int index, float pan);
+    float getPan(int index);
+
+    void setGain(int index, float gain);
+    float getGain(int index);
+
 private:
     // Oboe Audio Stream
     oboe::ManagedStream mAudioStream;
@@ -77,8 +83,8 @@
 
     // Sample Data
     int32_t mNumSampleBuffers;
-    std::vector<SampleBuffer*> mSampleBuffers;
-    std::vector<OneShotSampleSource*>   mSampleSources;
+    std::vector<SampleBuffer*>  mSampleBuffers;
+    std::vector<SampleSource*>  mSampleSources;
 
     bool    mOutputReset;
 };