94a9fec9843c — Chris Cannam 6 years ago
Initial import
A => .hgignore +5 -0
@@ 0,0 1,5 @@ 
+syntax: glob
+re:^obj/local/armeabi-v7a/
+re:^obj/local/armeabi/
+*.so
+*.~1~

          
A => .hgsub +1 -0
@@ 0,0 1,1 @@ 
+jni/rubberband = ssh://hg@bitbucket.org/breakfastquay/rubberband

          
A => .hgsubstate +1 -0
@@ 0,0 1,1 @@ 
+f12ee47e7f585c63a6d9939e5472fc62cf5f519d jni/rubberband

          
A => AndroidManifest.xml +15 -0
@@ 0,0 1,15 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.breakfastquay.rubberbandexample"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:label="@string/app_name" android:debuggable="true">
+        <activity android:name="RubberBandExampleActivity"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest> 

          
A => ant.properties +17 -0
@@ 0,0 1,17 @@ 
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+#  'source.dir' for the location of your java source folder and
+#  'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+#  'key.store' for the location of your keystore and
+#  'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+

          
A => build.xml +85 -0
@@ 0,0 1,85 @@ 
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="RubberBandExample" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <loadproperties srcFile="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
+            unless="sdk.dir"
+    />
+
+
+<!-- extension targets. Uncomment the ones where you want to do custom work
+     in between standard targets -->
+<!--
+    <target name="-pre-build">
+    </target>
+    <target name="-pre-compile">
+    </target>
+
+    /* This is typically used for code obfuscation.
+       Compiled code location: ${out.classes.absolute.dir}
+       If this is not done in place, override ${out.dex.input.absolute.dir} */
+    <target name="-post-compile">
+    </target>
+-->
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>

          
A => jni/Android.mk +28 -0
@@ 0,0 1,28 @@ 
+
+LOCAL_PATH := $(call my-dir)
+
+include $(LOCAL_PATH)/rubberband/Android.mk
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := testRubberBand
+LOCAL_MODULE_FILENAME := libtestRubberBand
+
+TARGET_ARCH_ABI	:=  armeabi-v7a
+LOCAL_ARM_NEON := true
+LOCAL_ARM_MODE := arm
+LOCAL_STATIC_LIBRARIES := cpufeatures
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/rubberband $(LOCAL_PATH)/rubberband/src
+
+LOCAL_SRC_FILES := testRubberBand.cpp
+
+#LOCAL_CFLAGS += -DWANT_TIMING -DFFT_MEASUREMENT
+
+LOCAL_SHARED_LIBRARIES = rubberband
+LOCAL_LDLIBS += -llog
+
+include $(BUILD_SHARED_LIBRARY)
+
+$(call import-module,android/cpufeatures)
+

          
A => jni/Application.mk +4 -0
@@ 0,0 1,4 @@ 
+APP_STL := stlport_static
+APP_ABI := armeabi-v7a
+
+APP_OPTIM := release

          
A => jni/testRubberBand.cpp +206 -0
@@ 0,0 1,206 @@ 
+
+#include "rubberband/RubberBandStretcher.h"
+
+#include <string.h>
+#include <jni.h>
+#include <time.h>
+#include <cpu-features.h>
+
+#include <string>
+#include "rubberband/src/dsp/FFT.h"
+#include "rubberband/src/base/Profiler.h"
+
+#include <android/log.h>
+#define D(x...) __android_log_print(ANDROID_LOG_INFO, "TestRubberBand", x)
+
+extern "C" {
+jstring
+Java_com_breakfastquay_rubberbandexample_RubberBandExampleActivity_testRubberBand(JNIEnv*, jobject);
+}
+
+using namespace RubberBand;
+
+static void printLong(std::string s)
+{
+    std::string bit;
+    int j = 0;
+    for (int i = 0; i < s.length(); ++i) {
+	if (s[i] == '\n') {
+	    if (i > j) {
+		D("%s", bit.c_str());
+		bit = "";
+	    }
+	    j = i + 1;
+	} else {
+	    bit += s[i];
+	}
+    }
+    if (bit != "") D("%s", bit.c_str());
+}
+
+jstring
+Java_com_breakfastquay_rubberbandexample_RubberBandExampleActivity_testRubberBand(JNIEnv* env, jobject thiz)
+{
+    char message[200];
+    
+    uint64_t features = android_getCpuFeatures();
+    D("CPU features: %d", (int)features);
+    if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) {
+        return env->NewStringUTF("Not an ARMv7 CPU\n");
+    }
+    if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) {
+	return env->NewStringUTF("CPU lacks NEON support\n");
+    }
+
+#ifdef FFT_MEASUREMENT
+    D("Running FFT tune...\n");
+    std::string fftreport = FFT::tune();
+    D("Report follows:\n");
+    printLong(fftreport);
+//    return env->NewStringUTF(fftreport.c_str());
+#endif
+
+    struct timeval begin;
+    gettimeofday(&begin, 0);
+
+#define SECONDS 30
+#define RATE 44100
+#define RATIO 1.01
+#define PITCHSHIFT 1.0
+#define SAMPLES (SECONDS * RATE)
+
+    D("Ratio %lf, pitch shift %lf, total input samples: %d\n", RATIO, PITCHSHIFT, SAMPLES);
+
+    float *input = new float[SAMPLES];
+
+    int insamples = SAMPLES;
+    int outsamples = insamples * RATIO;
+    
+    float irms = 0, orms = 0;
+
+    for (int i = 0; i < insamples; ++i) {
+	input[i] = sinf(float(i) / 100.f);
+    }
+
+    for (int i = 0; i < insamples; ++i) {
+//	input[i] = float(i % 100) / 50.f - 1.f;
+	irms += input[i] * input[i];
+    }
+    irms = sqrtf(irms / insamples);
+
+    RubberBandStretcher ts(RATE, 1,
+			   RubberBandStretcher::OptionProcessRealTime |
+			   RubberBandStretcher::OptionWindowShort,
+			   RATIO, PITCHSHIFT);
+
+    int outspace = outsamples + 44100;
+    float *output = new float[outspace];
+
+    if (!output) {
+	D("Failed to allocate space for %d samples\n", outspace);
+	return env->NewStringUTF("Allocation failed");
+    }
+
+    ts.setExpectedInputDuration(insamples);
+
+    int iin = 0, iout = 0;
+    int bs = 1024;
+    int avail = 0;
+
+    D("Total output samples: %d\n", outsamples);
+/*
+    while (iin < SAMPLES) {
+
+	int thisblock = SAMPLES - iin;
+	if (thisblock > bs) thisblock = bs;
+	float *iptr = input + iin;
+	ts.study(&iptr, thisblock, (iin + thisblock == SAMPLES));
+	iin += thisblock;
+	
+	D("Studied: %d\n", iin);
+    }
+
+    iin = 0;
+*/
+    
+    int printcounter = 0;
+
+    while (iin < SAMPLES) {
+
+	int thisblock = SAMPLES - iin;
+	if (thisblock > bs) thisblock = bs;
+	float *iptr = input + iin;
+	ts.process(&iptr, thisblock, (iin + thisblock == SAMPLES));
+	iin += thisblock;
+
+	if ((avail = ts.available()) > 0) {
+	    int thisout = avail;
+	    if (iout + thisout > outspace) thisout = outspace - iout;
+	    float *optr = output + iout;
+	    ts.retrieve(&optr, thisout);
+	    for (int i = 0; i < thisout; ++i) {
+		orms += optr[i] * optr[i];
+	    }
+	    iout += thisout;
+	}
+
+	if (++printcounter == 10) {
+	    D("Processed: %d\n", iout);
+	    printcounter = 0;
+	}
+    }
+
+    while ((avail = ts.available()) > 0) {
+	D("Available: %d\n", avail);
+	int thisout = avail;
+	if (iout + thisout > outspace) {
+	    D("iout = %d, thisout = %d, but outspace is only %d\n", iout, thisout, outspace);
+	    thisout = outspace - iout;
+	}
+	float *optr = output + iout;
+	ts.retrieve(&optr, thisout);
+	for (int i = 0; i < thisout; ++i) {
+	    orms += optr[i] * optr[i];
+	}
+	iout += thisout;
+	D("Processed: %d\n", iout);
+    }
+
+    D("Done, processed: %d\n", iout);
+    orms = sqrtf(orms / iout);
+
+    struct timeval end;
+    gettimeofday(&end, 0);
+
+    int secs = end.tv_sec - begin.tv_sec;
+    if (end.tv_usec < begin.tv_usec) --secs;
+
+    D(message, "iin = %d, iout = %d, in rms = %f, out rms = %f, elapsed = %d, in fps = %d, out fps = %d",
+      iin, iout, irms, orms, secs, iin / secs, iout / secs);
+
+    sprintf(message, "iin = %d, iout = %d, in rms = %f, out rms = %f, elapsed = %d, in fps = %d, out fps = %d",
+	    iin, iout, irms, orms, secs, iin / secs, iout / secs);
+
+    D("...");
+    D("0.2 sec from input:");
+    for (int i = 44100 * 10; i < 44100 * 10 + (44100 / 5); i += 4) {
+	D("%f %f %f %f", input[i], input[i+1], input[i+2], input[i+3]);
+    }
+
+    D("...");
+    D("0.2 sec from output:");
+    for (int i = 44100 * 10; i < 44100 * 10 + (44100 / 5); i += 4) {
+	D("%f %f %f %f", output[i], output[i+1], output[i+2], output[i+3]);
+    }
+
+    delete[] input;
+    delete[] output;
+
+#ifdef WANT_TIMING
+    std::string report = Profiler::getReport();
+    D("Done, report follows (%d chars):\n", report.length());
+    printLong(report);
+#endif
+
+    return env->NewStringUTF(message);
+}

          
A => local.properties +10 -0
@@ 0,0 1,10 @@ 
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked in Version Control Systems,
+# as it contains information specific to your local configuration.
+
+# location of the SDK. This is only used by Ant
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/opt/android-sdk-linux

          
A => proguard.cfg +40 -0
@@ 0,0 1,40 @@ 
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.app.Activity {
+   public void *(android.view.View);
+}
+
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+  public static final android.os.Parcelable$Creator *;
+}

          
A => project.properties +11 -0
@@ 0,0 1,11 @@ 
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-8

          
A => res/layout/main.xml +16 -0
@@ 0,0 1,16 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal">
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_play"
+	android:onClick="play"/>
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/button_stop"
+	android:onClick="stop"/>
+</LinearLayout>

          
A => res/values/strings.xml +6 -0
@@ 0,0 1,6 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">RubberBandExampleActivity</string>
+    <string name="button_play">Play</string>
+    <string name="button_stop">Stop</string>
+</resources>

          
A => src/com/breakfastquay/rubberband/RubberBandStretcher.java +109 -0
@@ 0,0 1,109 @@ 
+/* Copyright Chris Cannam - All Rights Reserved */
+
+package com.breakfastquay.rubberband;
+
+public class RubberBandStretcher
+{
+    public RubberBandStretcher(int sampleRate, int channels,
+			       int options,
+			       double initialTimeRatio,
+			       double initialPitchScale) {
+	handle = 0;
+	initialise(sampleRate, channels, options,
+		   initialTimeRatio, initialPitchScale);
+    }
+
+    public native void dispose();
+
+    public native void reset();
+
+    public native void setTimeRatio(double ratio);
+    public native void setPitchScale(double scale);
+
+    public native int getChannelCount();
+    public native double getTimeRatio();
+    public native double getPitchScale();
+
+    public native int getLatency();
+
+    public native void setTransientsOption(int options);
+    public native void setDetectorOption(int options);
+    public native void setPhaseOption(int options);
+    public native void setFormantOption(int options);
+    public native void setPitchOption(int options);
+
+    public native void setExpectedInputDuration(long samples);
+    public native void setMaxProcessSize(int samples);
+
+    public native int getSamplesRequired();
+
+    //!!! todo: setKeyFrameMap
+
+    public native void study(float[][] input, int offset, int n, boolean finalBlock);
+    public void study(float[][] input, boolean finalBlock) {
+	study(input, 0, input[0].length, finalBlock);
+    }
+
+    public native void process(float[][] input, int offset, int n, boolean finalBlock);
+    public void process(float[][] input, boolean finalBlock) {
+	process(input, 0, input[0].length, finalBlock);
+    }
+
+    public native int available();
+
+    public native int retrieve(float[][] output, int offset, int n);
+    public int retrieve(float[][] output) {
+	return retrieve(output, 0, output[0].length);
+    }
+
+    private native void initialise(int sampleRate, int channels, int options,
+				   double initialTimeRatio,
+				   double initialPitchScale);
+    private long handle;
+
+    public static final int OptionProcessOffline       = 0x00000000;
+    public static final int OptionProcessRealTime      = 0x00000001;
+
+    public static final int OptionStretchElastic       = 0x00000000;
+    public static final int OptionStretchPrecise       = 0x00000010;
+    
+    public static final int OptionTransientsCrisp      = 0x00000000;
+    public static final int OptionTransientsMixed      = 0x00000100;
+    public static final int OptionTransientsSmooth     = 0x00000200;
+
+    public static final int OptionDetectorCompound     = 0x00000000;
+    public static final int OptionDetectorPercussive   = 0x00000400;
+    public static final int OptionDetectorSoft         = 0x00000800;
+
+    public static final int OptionPhaseLaminar         = 0x00000000;
+    public static final int OptionPhaseIndependent     = 0x00002000;
+    
+    public static final int OptionThreadingAuto        = 0x00000000;
+    public static final int OptionThreadingNever       = 0x00010000;
+    public static final int OptionThreadingAlways      = 0x00020000;
+
+    public static final int OptionWindowStandard       = 0x00000000;
+    public static final int OptionWindowShort          = 0x00100000;
+    public static final int OptionWindowLong           = 0x00200000;
+
+    public static final int OptionSmoothingOff         = 0x00000000;
+    public static final int OptionSmoothingOn          = 0x00800000;
+
+    public static final int OptionFormantShifted       = 0x00000000;
+    public static final int OptionFormantPreserved     = 0x01000000;
+
+    public static final int OptionPitchHighSpeed       = 0x00000000;
+    public static final int OptionPitchHighQuality     = 0x02000000;
+    public static final int OptionPitchHighConsistency = 0x04000000;
+
+    public static final int OptionChannelsApart        = 0x00000000;
+    public static final int OptionChannelsTogether     = 0x10000000;
+
+    public static final int DefaultOptions             = 0x00000000;
+    public static final int PercussiveOptions          = 0x00102000;
+
+    static {
+	System.loadLibrary("rubberband");
+    }
+};
+

          
A => src/com/breakfastquay/rubberbandexample/RubberBandExampleActivity.java +219 -0
@@ 0,0 1,219 @@ 
+package com.breakfastquay.rubberbandexample;
+
+import android.app.Activity;
+import android.view.View;
+import android.widget.TextView;
+import android.os.Bundle;
+import android.media.AudioTrack;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.content.res.Resources;
+import android.util.Log;
+import android.util.TimingLogger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Collections;
+
+import java.nio.ByteBuffer;
+
+import com.breakfastquay.rubberband.RubberBandStretcher;
+
+public class RubberBandExampleActivity extends Activity
+{
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+
+	int rate = 44100;
+
+	m_audioTrackBufferSize = 
+	    AudioTrack.getMinBufferSize(rate,
+					AudioFormat.CHANNEL_OUT_STEREO,
+					AudioFormat.ENCODING_PCM_16BIT);
+	
+	m_audioTrackBufferSize *= 4;
+	
+	m_track = new AudioTrack
+	    (AudioManager.STREAM_MUSIC,
+	     rate,
+	     AudioFormat.CHANNEL_OUT_STEREO,
+	     AudioFormat.ENCODING_PCM_16BIT,
+	     m_audioTrackBufferSize,
+	     AudioTrack.MODE_STREAM);
+
+	m_buflist = Collections.synchronizedList(new LinkedList<short[]>());
+
+	m_playThread = new Thread(m_playback);
+	m_playThread.start();
+	
+	m_popThread = new Thread(m_populate);
+	m_popThread.start();
+
+	setContentView(R.layout.main);
+    }
+
+    List<short[]> m_buflist;
+    AudioTrack m_track;
+    int m_audioTrackBufferSize;
+    Thread m_playThread;
+    Thread m_popThread;
+    boolean m_cancelled = false;
+
+    Runnable m_playback = new Runnable()
+    {
+	public void run()
+	{
+	    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+
+	    while (true) {
+		try {
+		    synchronized(m_buflist) {
+			while (m_buflist.isEmpty() && !m_cancelled) {
+			    m_buflist.wait();
+			}
+		    }
+		} catch (java.lang.InterruptedException e) {
+		    Log.d("RubberBand", "run(): wait interrupted");
+		    continue;
+		}
+		if (m_cancelled) break;
+		Log.d("RubberBand", m_buflist.size() + " element(s) in buflist");
+		short[] buf = m_buflist.get(0);
+		m_buflist.remove(0);
+		m_track.write(buf, 0, buf.length);
+	    }
+	}
+    };
+
+    Runnable m_populate = new Runnable()
+    {
+	public void run()
+	{
+	    try {
+		int rate = 44100;
+		double ratio = 1.6;
+		double pitchshift = 1.2;
+
+		InputStream audio = getResources().openRawResource(R.raw.a);
+		ByteArrayOutputStream ostr = new ByteArrayOutputStream(audio.available());
+		byte[] buf = new byte[1024];
+		int r;
+		while ((r = audio.read(buf)) >= 0) ostr.write(buf, 0, r);
+		byte[] raw = ostr.toByteArray();
+ 
+		long start = System.currentTimeMillis();
+
+		// We assume raw is interleaved 2-channel 16-bit PCM
+		int channels = 2;
+		int frames = raw.length / (channels * 2);
+
+		int insamples = frames;
+
+		RubberBandStretcher ts = new RubberBandStretcher
+		    (rate, channels,
+		     RubberBandStretcher.OptionProcessRealTime +
+		     RubberBandStretcher.OptionPitchHighSpeed +
+		     RubberBandStretcher.OptionWindowLong,
+		     ratio, pitchshift);
+
+		ts.setExpectedInputDuration(insamples);
+
+		int iin = 0, iout = 0;
+		int bs = 1024;
+		int avail = 0;
+
+		float[][] inblocks = new float[channels][bs];
+
+		ByteBuffer bb = ByteBuffer.wrap(raw).order(null);
+
+		int ppc = 0;
+
+		int audioTrackBufferFrames = m_audioTrackBufferSize / 4;
+		float[][] outblocks = new float[channels][audioTrackBufferFrames];
+
+		Log.d("RubberBand", "audioTrackBufferFrames = " + audioTrackBufferFrames);
+
+		// don't stack up more than 2 sec in buffers
+		int maxBufs = rate / bs;
+		int maxBufsSleep = 1000; // ms
+
+		while (iin < insamples) {
+
+		    if (m_buflist.size() > maxBufs) {
+			try {
+			    Log.d("RubberBand", "reached " + maxBufs + " bufs, sleeping " + maxBufsSleep);
+			    Thread.sleep(maxBufsSleep);
+			} catch (java.lang.InterruptedException e) {
+			}
+		    }
+
+		    int pc = (100 * iin) / insamples;
+		    if (pc != ppc) {
+			Log.d("RubberBand", iin + " of " + insamples + " [" + pc + "%]");
+			ppc = pc;
+		    }
+
+		    int thisblock = insamples - iin;
+		    if (thisblock > bs) thisblock = bs;
+
+		    for (int i = 0; i < thisblock; ++i) {
+			for (int c = 0; c < channels; ++c) {
+			    short val = bb.getShort();
+			    inblocks[c][i] = (float)val / 32768;
+			}
+		    }
+
+		    ts.process(inblocks, (iin + thisblock == insamples));
+		    iin += thisblock;
+
+		    while ((avail = ts.available()) > 0) {
+
+			int retrieved = ts.retrieve(outblocks);
+			short[] outbuf = new short[retrieved * channels];
+			for (int i = 0; i < retrieved; ++i) {
+			    for (int c = 0; c < channels; ++c) {
+				float f = outblocks[c][i];
+				if (f < -1.0f) f = -1.0f;
+				if (f > 1.0f) f = 1.0f;
+				int ox = i * channels + c;
+				outbuf[ox] = (short)(f * 32767);
+			    }
+			}
+			synchronized(m_buflist) {
+			    m_buflist.add(outbuf);
+			    m_buflist.notify();
+			}
+			iout += retrieved;
+		    }
+		}
+
+		ts.dispose();
+
+		long end = System.currentTimeMillis();
+		double t = (double)(end - start) / 1000;
+		Log.d("RubberBand",
+		      "iin = " + iin + ", iout = " + iout + ", time = "
+		      + t + ", in fps = " + iin/t + ", out fps = " + iout/t);
+
+	    } catch (IOException e) { }
+	}
+    };
+
+    public void play(View v) {
+	Log.d("RubberBand", "play");
+	m_track.play();
+    }
+
+    public void stop(View v) {
+	Log.d("RubberBand", "stop");
+	m_track.flush();
+	m_track.stop();
+    }
+}