Index: .travis.yml
===================================================================
--- .travis.yml	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ .travis.yml	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -42,10 +42,10 @@
       osx_image: xcode8
       compiler: clang
-      env: WAFOPTS="--with-target-platform=ios --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
+      env: WAFOPTS="--with-target-platform=ios --disable-avcodec --disable-sndfile --disable-samplerate --disable-rubberband" AUBIO_NOTESTS=1
     - language: C
       os: osx
       osx_image: xcode8
       compiler: clang
-      env: WAFOPTS="--with-target-platform=iosimulator --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
+      env: WAFOPTS="--enable-fat --disable-avcodec --disable-sndfile --disable-samplerate --disable-rubberband" AUBIO_NOTESTS=1
     - language: C
       os: osx
@@ -81,4 +81,5 @@
     - libasound2-dev
     - libfftw3-dev
+    - librubberband-dev
     - sox
 
@@ -90,4 +91,6 @@
        brew install ffmpeg
        brew install libsndfile
+       brew install libsamplerate
+       brew install rubberband
        export PATH="$HOME/Library/Python/2.7/bin/:$PATH"
      fi;
Index: python/demos/demo_pitchshift.py
===================================================================
--- python/demos/demo_pitchshift.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ python/demos/demo_pitchshift.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,52 @@
+#! /usr/bin/env python
+
+import sys
+import aubio
+
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        print('usage: %s <inputfile> <outputfile> [transpose] [samplerate] [hop_size] [mode]' % sys.argv[0])
+        print('available modes: default, crispness:0, crispness:1, ... crispness:6')
+        sys.exit(1)
+    if len(sys.argv) > 3: transpose = float(sys.argv[3])
+    else: transpose = 12.
+    if len(sys.argv) > 4: samplerate = int(sys.argv[4])
+    else: samplerate = 0
+    if len(sys.argv) > 5: hop_size = int(sys.argv[5])
+    else: hop_size = 64
+    if len(sys.argv) > 6: mode = sys.argv[6]
+    else: mode = "default"
+
+    source_read = aubio.source(sys.argv[1], samplerate, hop_size)
+    if samplerate == 0: samplerate = source_read.samplerate
+    sink_out = aubio.sink(sys.argv[2], samplerate)
+
+    pitchshifter = aubio.pitchshift(mode, 1., hop_size, samplerate)
+    if transpose: pitchshifter.set_transpose(transpose)
+
+    total_frames, read = 0, hop_size
+    transpose_range = 23.9
+    while read:
+        vec, read = source_read()
+        # transpose the samples
+        out = pitchshifter(vec)
+        # position in the file (between 0. and 1.)
+        percent_read = total_frames / float(source_read.duration)
+        # variable transpose rate (in semitones)
+        transpose = 2 * transpose_range * percent_read - transpose_range
+        # set transpose rate
+        pitchshifter.set_transpose(transpose)
+        # print the transposition
+        #print pitchshifter.get_transpose()
+        # write the output
+        sink_out(out, read)
+        total_frames += read
+
+    # end of file, print some results
+    outstr = "wrote %.2fs" % (total_frames / float(samplerate))
+    outstr += " (%d frames in" % total_frames
+    outstr += " %d blocks" % (total_frames // source_read.hop_size)
+    outstr += " at %dHz)" % source_read.samplerate
+    outstr += " from " + source_read.uri
+    outstr += " to " + sink_out.uri
+    print(outstr)
Index: python/lib/gen_code.py
===================================================================
--- python/lib/gen_code.py	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ python/lib/gen_code.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -20,4 +20,7 @@
     'method': '"default"',
     'uri': '"none"',
+    'transpose': '0.',
+    'maxwrite': 1024,
+    'maxrequest': 1024,
     }
 
@@ -83,4 +86,6 @@
         'filterbank': 'self->n_filters',
         'tss': 'self->buf_size',
+        'pitchshift': 'self->hop_size',
+        'ringbuffer': 'self->maxrequest',
         }
 
@@ -95,4 +100,6 @@
         'wavetable': 'self->hop_size',
         'tss': 'self->buf_size / 2 + 1',
+        'pitchshift': 'self->hop_size',
+        'ringbuffer': 'self->maxwrite',
         }
 
@@ -266,4 +273,6 @@
         if p['type'] == 'char_t*':
             return self.check_valid_char(p)
+        if p['type'] == 'smpl_t':
+            return self.check_valid_smpl(p)
         else:
             print ("ERROR, no idea how to check %s for validity" % p['type'])
@@ -286,4 +295,13 @@
     self->{name} = {defval};
     if ({name} != NULL) {{
+        self->{name} = {name};
+    }}
+""".format(defval = aubiodefvalue[name], name = name)
+
+    def check_valid_smpl(self, p):
+        name = p['name']
+        return """
+    self->{name} = {defval};
+    if ({name} != 0.) {{
         self->{name} = {name};
     }}
Index: python/lib/gen_external.py
===================================================================
--- python/lib/gen_external.py	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ python/lib/gen_external.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -40,4 +40,7 @@
   #'sampler',
   'audio_unit',
+  'timestretch', # TODO fix parsing of uint_t *read in _do
+  'sampler', # TODO fix parsing of uint_t *read in _do
+  'ringbuffer',
   ]
 
Index: python/lib/moresetuptools.py
===================================================================
--- python/lib/moresetuptools.py	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ python/lib/moresetuptools.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -115,6 +115,7 @@
     packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample',
                 'jack',
-                'jack',
                 'sndfile',
+                'samplerate',
+                'rubberband',
                 #'fftw3f',
                ]
@@ -136,4 +137,6 @@
     if 'samplerate' in ext.libraries:
         ext.define_macros += [('HAVE_SAMPLERATE', 1)]
+    if 'rubberband' in ext.libraries:
+        ext.define_macros += [('HAVE_RUBBERBAND', 1)]
     if 'fftw3f' in ext.libraries:
         ext.define_macros += [('HAVE_FFTW3F', 1)]
Index: python/tests/test_pitchshift.py
===================================================================
--- python/tests/test_pitchshift.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ python/tests/test_pitchshift.py	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,101 @@
+#! /usr/bin/env python
+
+from numpy.testing import TestCase
+from nose2.tools import params
+import numpy as np
+import aubio
+
+class aubio_pitchshift(TestCase):
+
+    def setUp(self):
+        try:
+            self.o = aubio.pitchshift(hop_size = 128)
+        except RuntimeError as e:
+            self.skipTest("creating aubio.pitchshift {}".format(e))
+
+    def test_default_creation(self):
+        self.assertEqual(self.o.get_pitchscale(), 1)
+        self.assertEqual(self.o.get_transpose(), 0)
+
+    def test_on_zeros(self):
+        test_length = self.o.hop_size * 100
+        read = 0
+        # test on zeros
+        vec = aubio.fvec(self.o.hop_size)
+        transpose_range = 24
+        while read < test_length:
+            # transpose the samples
+            out = self.o(vec)
+            self.assertTrue((out == 0).all())
+            # position in the file (between 0. and 1.)
+            percent_read = read / float(test_length)
+            # variable transpose rate (in semitones)
+            transpose = 2 * transpose_range * percent_read - transpose_range
+            # set transpose rate
+            self.o.set_transpose(transpose)
+            read += len(vec)
+
+    def test_on_ones(self):
+        test_length = self.o.hop_size * 100
+        read = 0
+        # test on zeros
+        vec = aubio.fvec(self.o.hop_size) + 1
+        transpose_range = 1.24
+        while read < test_length:
+            # transpose the samples
+            out = self.o(vec)
+            # position in the file (between 0. and 1.)
+            percent_read = read / float(test_length)
+            # variable transpose rate (in semitones)
+            transpose = 2 * transpose_range * percent_read - transpose_range
+            # set transpose rate
+            self.o.set_transpose(transpose)
+            read += len(vec)
+
+    def test_transpose_too_high(self):
+        with self.assertRaises(ValueError):
+            self.o.set_transpose(24.3)
+
+    def test_transpose_too_low(self):
+        with self.assertRaises(ValueError):
+            self.o.set_transpose(-24.3)
+
+class aubio_pitchshift_wrong_params(TestCase):
+
+    def test_wrong_transpose(self):
+        with self.assertRaises(RuntimeError):
+            aubio.pitchshift("default", -123)
+
+class aubio_pitchshift_testruns(TestCase):
+
+    @params(
+            ("default",     1.2,  128,  44100),
+            ("crispness:0", 0.43,  64,   8000),
+            ("crispness:3", 0.53, 256,   8000),
+            ("crispness:3", 1.53, 512,   8000),
+            ("crispness:6", 2.3, 4096, 192000),
+            )
+    def test_run_with_params(self, mode, pitchscale, hop_size, samplerate):
+        try:
+            self.o = aubio.pitchshift(mode, pitchscale, hop_size, samplerate)
+        except RuntimeError as e:
+            self.skipTest("failed creating pitchshift ({})".format(e))
+        test_length = self.o.hop_size * 50
+        read = 0
+        # test on random
+        vec = np.random.rand(self.o.hop_size).astype(aubio.float_type)
+        transpose_range = self.o.get_transpose()
+        while read < test_length:
+            # transpose the samples
+            out = self.o(vec)
+            # position in the file (between 0. and 1.)
+            percent_read = read / float(test_length)
+            # variable transpose rate (in semitones)
+            transpose =  transpose_range - 2 * transpose_range * percent_read
+            # set transpose rate
+            self.o.set_transpose(transpose)
+            read += len(vec)
+
+if __name__ == '__main__':
+    from nose2 import main
+    main()
Index: src/aubio.h
===================================================================
--- src/aubio.h	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ src/aubio.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -218,6 +218,9 @@
 #include "pitch/pitchspecacf.h"
 #include "tempo/beattracking.h"
+#include "effects/pitchshift.h"
+#include "effects/timestretch.h"
 #include "utils/scale.h"
 #include "utils/hist.h"
+#include "utils/ringbuffer.h"
 #endif
 
Index: src/effects/pitchshift.h
===================================================================
--- src/effects/pitchshift.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/pitchshift.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,122 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_PITCHSHIFT_H
+#define AUBIO_PITCHSHIFT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \file
+
+  Pitch shifting object
+
+  ::aubio_pitchshift_t can be used to transpose a stream of blocks of frames.
+
+  \example effects/test-pitchshift.c
+
+*/
+
+/** pitch shifting object */
+typedef struct _aubio_pitchshift_t aubio_pitchshift_t;
+
+/** execute pitch shifting on an input signal frame
+
+  \param o pitch shifting object as returned by new_aubio_pitchshift()
+  \param in input signal of size [hop_size]
+  \param out output pitch candidates of size [1]
+
+*/
+void aubio_pitchshift_do (aubio_pitchshift_t * o, const fvec_t * in, fvec_t * out);
+
+/** deletion of the pitch shifting object
+
+  \param o pitch shifting object as returned by new_aubio_pitchshift()
+
+*/
+void del_aubio_pitchshift (aubio_pitchshift_t * o);
+
+/** creation of the pitch shifting object
+
+  \param method set pitch shifting algorithm ("default")
+  \param transpose initial pitch transposition
+  \param hop_size step size between two consecutive analysis instant
+  \param samplerate sampling rate of the signal
+
+  \return newly created ::aubio_pitchshift_t
+
+*/
+aubio_pitchshift_t *new_aubio_pitchshift (const char_t * method,
+    smpl_t transpose, uint_t hop_size, uint_t samplerate);
+
+/** get the latency of the pitch shifting object, in samples
+
+  \param o pitch shifting object as returned by ::new_aubio_pitchshift()
+
+  \return latency of the pitch shifting object in samples
+
+*/
+uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * o);
+
+/** set the pitch scale of the pitch shifting object
+
+  \param o pitch shifting object as returned by new_aubio_pitchshift()
+  \param pitchscale new pitch scale of the pitch shifting object
+
+  \return 0 if successfull, non-zero otherwise
+
+*/
+uint_t aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * o, smpl_t pitchscale);
+
+/** get the pitchscale of the pitch shifting object
+
+  \param o pitch shifting object as returned by ::new_aubio_pitchshift()
+
+  \return pitchscale of the pitch shifting object
+
+*/
+smpl_t aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * o);
+
+/** set the transposition of the pitch shifting object, in semitones
+
+  \param o pitch shifting object as returned by new_aubio_pitchshift()
+  \param transpose new pitch transposition of the pitch shifting object, expressed
+  in semitones (should be in the range [-24;+24])
+
+  \return 0 if successfull, non-zero otherwise
+
+*/
+uint_t aubio_pitchshift_set_transpose (aubio_pitchshift_t * o, smpl_t transpose);
+
+/** get the transposition of the pitch shifting object, in semitones
+
+  \param o pitch shifting object as returned by ::new_aubio_pitchshift()
+
+  \return transposition of the pitch shifting object, in semitones
+
+*/
+smpl_t aubio_pitchshift_get_transpose (aubio_pitchshift_t * o);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_PITCHSHIFT_H */
Index: src/effects/pitchshift_dummy.c
===================================================================
--- src/effects/pitchshift_dummy.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/pitchshift_dummy.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,74 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "config.h"
+
+#ifndef HAVE_RUBBERBAND
+
+#include "aubio_priv.h"
+#include "fvec.h"
+#include "effects/pitchshift.h"
+
+// TODO fallback pitch shifting implementation
+
+struct _aubio_pitchshift_t
+{
+  void *dummy;
+};
+
+void aubio_pitchshift_do (aubio_pitchshift_t * o UNUSED, const fvec_t * in UNUSED,
+    fvec_t * out UNUSED) {
+}
+
+void del_aubio_pitchshift (aubio_pitchshift_t * o UNUSED) {
+}
+
+aubio_pitchshift_t *new_aubio_pitchshift (const char_t * method UNUSED,
+    smpl_t pitchscale UNUSED, uint_t hop_size UNUSED, uint_t samplerate UNUSED)
+{
+  AUBIO_ERR ("aubio was not compiled with rubberband\n");
+  return NULL;
+}
+
+uint_t aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * o UNUSED, smpl_t pitchscale UNUSED)
+{
+  return AUBIO_FAIL;
+}
+
+smpl_t aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * o UNUSED)
+{
+  return 1.;
+}
+
+uint_t aubio_pitchshift_set_transpose (aubio_pitchshift_t * o UNUSED, smpl_t transpose UNUSED) {
+  return AUBIO_FAIL;
+}
+
+smpl_t aubio_pitchshift_get_transpose (aubio_pitchshift_t * o UNUSED) {
+  return 0.;
+}
+
+uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * o UNUSED) {
+  return 0.;
+}
+
+// end of dummy implementation
+
+#endif /* HAVE_RUBBERBAND */
Index: src/effects/pitchshift_rubberband.c
===================================================================
--- src/effects/pitchshift_rubberband.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/pitchshift_rubberband.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,154 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "config.h"
+
+#ifdef HAVE_RUBBERBAND
+
+#include "aubio_priv.h"
+#include "fvec.h"
+#include "effects/pitchshift.h"
+
+#include "rubberband/rubberband-c.h"
+
+/** generic pitch shifting structure */
+struct _aubio_pitchshift_t
+{
+  uint_t samplerate;              /**< samplerate */
+  uint_t hopsize;                 /**< hop size */
+  smpl_t pitchscale;              /**< pitch scale */
+
+  RubberBandState rb;
+  RubberBandOptions rboptions;
+};
+
+extern RubberBandOptions aubio_get_rubberband_opts(const char_t *mode);
+
+aubio_pitchshift_t *
+new_aubio_pitchshift (const char_t * mode,
+    smpl_t transpose, uint_t hopsize, uint_t samplerate)
+{
+  aubio_pitchshift_t *p = AUBIO_NEW (aubio_pitchshift_t);
+  p->samplerate = samplerate;
+  p->hopsize = hopsize;
+  p->pitchscale = 1.;
+  if (transpose >= -24. && transpose <= 24.) {
+    p->pitchscale = POW(2., transpose / 12.);
+  } else {
+    AUBIO_ERR("pitchshift: transpose should be in the range [-24, 24], got %f\n", transpose);
+    goto beach;
+  }
+
+  p->rboptions = aubio_get_rubberband_opts(mode);
+  if (p->rboptions < 0) {
+    AUBIO_ERR("pitchshift: unknown pitch shifting method %s\n", mode);
+    goto beach;
+  }
+  //AUBIO_MSG("pitchshift: using pitch shifting method %s\n", mode);
+
+  p->rb = rubberband_new(samplerate, 1, p->rboptions, 1., p->pitchscale);
+  rubberband_set_max_process_size(p->rb, p->hopsize);
+  //rubberband_set_debug_level(p->rb, 10);
+
+#if 1
+  // warm up rubber band
+  unsigned int latency = MAX(p->hopsize, rubberband_get_latency(p->rb));
+  int available = rubberband_available(p->rb);
+  fvec_t *zeros = new_fvec(p->hopsize);
+  while (available <= (int)latency) {
+    rubberband_process(p->rb, (const float* const*)&(zeros->data), p->hopsize, 0);
+    available = rubberband_available(p->rb);
+  }
+  del_fvec(zeros);
+#endif
+
+  return p;
+
+beach:
+  del_aubio_pitchshift(p);
+  return NULL;
+}
+
+void
+del_aubio_pitchshift (aubio_pitchshift_t * p)
+{
+  if (p->rb) {
+    rubberband_delete(p->rb);
+  }
+  AUBIO_FREE (p);
+}
+
+uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * p) {
+  return rubberband_get_latency(p->rb);
+}
+
+uint_t
+aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * p, smpl_t pitchscale)
+{
+  if (pitchscale >= 0.25  && pitchscale <= 4.) {
+    p->pitchscale = pitchscale;
+    rubberband_set_pitch_scale(p->rb, p->pitchscale);
+    return AUBIO_OK;
+  } else {
+    AUBIO_ERR("pitchshift: could not set pitchscale to %.2f\n", pitchscale);
+    return AUBIO_FAIL;
+  }
+}
+
+smpl_t
+aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * p)
+{
+  return p->pitchscale;
+}
+
+uint_t
+aubio_pitchshift_set_transpose(aubio_pitchshift_t * p, smpl_t transpose)
+{
+  if (transpose >= -24. && transpose <= 24.) {
+    smpl_t pitchscale = POW(2., transpose / 12.);
+    return aubio_pitchshift_set_pitchscale(p, pitchscale);
+  } else {
+    AUBIO_ERR("pitchshift: could not set transpose to %.2f\n", transpose);
+    return AUBIO_FAIL;
+  }
+}
+
+smpl_t
+aubio_pitchshift_get_transpose(aubio_pitchshift_t * p)
+{
+  return 12. * LOG(p->pitchscale) / LOG(2.0);
+}
+
+void
+aubio_pitchshift_do (aubio_pitchshift_t * p, const fvec_t * in, fvec_t * out)
+{
+  int output = 0;
+  rubberband_process(p->rb, (const float* const*)&(in->data), p->hopsize, output);
+  if (rubberband_available(p->rb) >= (int)p->hopsize) {
+    rubberband_retrieve(p->rb, (float* const*)&(out->data), p->hopsize);
+  } else {
+    AUBIO_WRN("pitchshift: catching up with zeros, only %d available, needed: %d, "
+        "current pitchscale: %f\n",
+        rubberband_available(p->rb), p->hopsize, p->pitchscale);
+    fvec_zeros(out);
+  }
+}
+
+#endif
Index: src/effects/rubberband_utils.c
===================================================================
--- src/effects/rubberband_utils.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/rubberband_utils.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,58 @@
+
+
+#include "config.h"
+#include "aubio_priv.h"
+
+#ifdef HAVE_RUBBERBAND
+
+#include "rubberband/rubberband-c.h"
+
+// check rubberband is 1.8.1, warn if 1.3
+#if !((RUBBERBAND_API_MAJOR_VERSION >= 2) && \
+    (RUBBERBAND_API_MINOR_VERSION >= 5))
+#warning RubberBandOptionDetectorSoft not available, \
+ please upgrade rubberband to version 1.8.1 or higher
+#define RubberBandOptionDetectorSoft 0x00000000
+#endif
+
+RubberBandOptions aubio_get_rubberband_opts(const char_t *mode)
+{
+  RubberBandOptions rboptions = RubberBandOptionProcessRealTime;
+
+  if ( strcmp(mode,"crispness:0") == 0 ) {
+    rboptions |= RubberBandOptionTransientsSmooth;
+    rboptions |= RubberBandOptionWindowLong;
+    rboptions |= RubberBandOptionPhaseIndependent;
+  } else if ( strcmp(mode, "crispness:1") == 0 ) {
+    rboptions |= RubberBandOptionDetectorSoft;
+    rboptions |= RubberBandOptionTransientsSmooth;
+    rboptions |= RubberBandOptionWindowLong;
+    rboptions |= RubberBandOptionPhaseIndependent;
+  } else if ( strcmp(mode, "crispness:2") == 0 ) {
+    rboptions |= RubberBandOptionTransientsSmooth;
+    rboptions |= RubberBandOptionPhaseIndependent;
+  } else if ( strcmp(mode, "crispness:3") == 0 ) {
+    rboptions |= RubberBandOptionTransientsSmooth;
+  } else if ( strcmp(mode, "crispness:4") == 0 ) {
+    // same as "default"
+  } else if ( strcmp(mode, "crispness:5") == 0 ) {
+    rboptions |= RubberBandOptionTransientsCrisp;
+  } else if ( strcmp(mode, "crispness:6") == 0 ) {
+    rboptions |= RubberBandOptionTransientsCrisp;
+    rboptions |= RubberBandOptionWindowShort;
+    rboptions |= RubberBandOptionPhaseIndependent;
+  } else if ( strcmp(mode, "default") == 0 ) {
+    // nothing to do
+  } else {
+    // failed parsing option string
+    return -1;
+  }
+  // other options to include
+  //p->rboptions |= RubberBandOptionWindowStandard;
+  //p->rboptions |= RubberBandOptionSmoothingOff;
+  //p->rboptions |= RubberBandOptionFormantShifted;
+  //p->rboptions |= RubberBandOptionPitchHighConsistency;
+  return rboptions;
+}
+
+#endif
Index: src/effects/timestretch.h
===================================================================
--- src/effects/timestretch.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/timestretch.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,191 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef AUBIO_TIMESTRETCH_H
+#define AUBIO_TIMESTRETCH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \file
+
+  time stretching object
+
+  ::aubio_timestretch_t can be used to open a source file, read samples from
+  it, time-stretch them, and write out the modified samples.
+
+  The time-stretching factor can be changed at any time using
+  aubio_timestretch_set_stretch().
+
+  A transposition can also be applied and changed at any time with
+  aubio_timestretch_set_transpose().
+
+  \example effects/test-timestretch.c
+
+*/
+
+/** time stretching object */
+typedef struct _aubio_timestretch_t aubio_timestretch_t;
+
+/** execute time stretching on an input signal frame
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+  \param out timestretched output of size [hop_size]
+  \param read number of frames actually wrote out
+
+*/
+void aubio_timestretch_do (aubio_timestretch_t * o, fvec_t * out, uint_t * read);
+
+/** deletion of the time stretching object
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+
+*/
+void del_aubio_timestretch (aubio_timestretch_t * o);
+
+/** creation of the time stretching object
+
+  \param method time stretching algorithm ("default")
+  \param stretch initial time stretching factor
+  \param hop_size block size at which the frames should be produced
+  \param samplerate sampling rate of the signal
+
+  \return newly created ::aubio_timestretch_t
+
+*/
+aubio_timestretch_t *new_aubio_timestretch (const char_t * method,
+    smpl_t stretch, uint_t hop_size, uint_t samplerate);
+
+/** push length samples from in to time stretching object
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+  \param in input vector of new samples to push to time stretching object
+  \param length number of new samples to push from input vector
+
+  \return number of currently available samples
+
+ */
+sint_t aubio_timestretch_push(aubio_timestretch_t * o, fvec_t *in, uint_t length);
+
+/** get number of currently available samples from time stretching object
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return number of currently available samples
+
+ */
+sint_t aubio_timestretch_get_available(aubio_timestretch_t * o);
+
+/** get the latency of the time stretching object, in samples
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return latency of the time stretching object in samples
+
+*/
+uint_t aubio_timestretch_get_latency (aubio_timestretch_t * o);
+
+/** get the samplerate of the time stretching object
+
+  Call after new_aubio_timestretch() was called with 0 to match the original
+  samplerate of the input file.
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+
+  \return samplerate of the time stretching object
+
+ */
+uint_t aubio_timestretch_get_samplerate (aubio_timestretch_t * o);
+
+/** set the stretching ratio of the time stretching object
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+  \param stretch new time stretching ratio of the time stretching object
+  (should be in the range [0.025; 10.])
+
+  \return 0 if successfull, non-zero otherwise
+
+*/
+uint_t aubio_timestretch_set_stretch (aubio_timestretch_t * o, smpl_t stretch);
+
+/** get the transposition of the time stretching object, in semitones
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return time stretching ratio of the time stretching object, in the range
+  [0.025; 10.]
+
+*/
+smpl_t aubio_timestretch_get_stretch (aubio_timestretch_t * o);
+
+/** set the pitch scale of the time stretching object
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+  \param pitchscale new pitch scale of the time stretching object
+
+  \return 0 if successfull, non-zero otherwise
+
+*/
+uint_t aubio_timestretch_set_pitchscale (aubio_timestretch_t * o, smpl_t pitchscale);
+
+/** get the pitchscale of the time stretching object
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return pitchscale of the time stretching object
+
+*/
+smpl_t aubio_timestretch_get_pitchscale (aubio_timestretch_t * o);
+
+/** set the transposition of the time stretching object, in semitones
+
+  \param o time stretching object as returned by new_aubio_timestretch()
+  \param transpose new pitch transposition of the time stretching object, expressed
+  in semitones (should be in the range [-24;+24])
+
+  \return 0 if successfull, non-zero otherwise
+
+*/
+uint_t aubio_timestretch_set_transpose (aubio_timestretch_t * o, smpl_t transpose);
+
+/** get the transposition of the time stretching object, in semitones
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return transposition of the time stretching object, in semitones
+
+*/
+smpl_t aubio_timestretch_get_transpose (aubio_timestretch_t * o);
+
+/** reset the time stretching object
+
+  \param o time stretching object as returned by ::new_aubio_timestretch()
+
+  \return 0 on success, non-zero otherwise
+
+*/
+uint_t aubio_timestretch_reset(aubio_timestretch_t * o);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_TIMESTRETCH_H */
Index: src/effects/timestretch_dummy.c
===================================================================
--- src/effects/timestretch_dummy.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/timestretch_dummy.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,103 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "config.h"
+
+#ifndef HAVE_RUBBERBAND
+
+#include "aubio_priv.h"
+#include "fvec.h"
+#include "effects/timestretch.h"
+
+// TODO fallback time stretching implementation
+
+struct _aubio_timestretch_t
+{
+  void *dummy;
+};
+
+void
+aubio_timestretch_do (aubio_timestretch_t * o UNUSED, fvec_t * out UNUSED,
+    uint_t * read UNUSED)
+{
+}
+
+void del_aubio_timestretch (aubio_timestretch_t * o UNUSED) {
+}
+
+aubio_timestretch_t *
+new_aubio_timestretch (const char_t * method UNUSED,
+    smpl_t pitchscale UNUSED, uint_t hop_size UNUSED, uint_t samplerate UNUSED)
+{
+  AUBIO_ERR ("timestretch: aubio was not compiled with rubberband\n");
+  return NULL;
+}
+
+uint_t aubio_timestretch_set_stretch (aubio_timestretch_t * o UNUSED, smpl_t stretch UNUSED)
+{
+  return AUBIO_FAIL;
+}
+
+smpl_t aubio_timestretch_get_stretch (aubio_timestretch_t * o UNUSED)
+{
+  return 1.;
+}
+
+uint_t aubio_timestretch_set_pitchscale (aubio_timestretch_t * o UNUSED, smpl_t pitchscale UNUSED)
+{
+  return AUBIO_FAIL;
+}
+
+uint_t aubio_timestretch_get_samplerate (aubio_timestretch_t * o UNUSED) {
+  return 0;
+}
+
+smpl_t aubio_timestretch_get_pitchscale (aubio_timestretch_t * o UNUSED)
+{
+  return 1.;
+}
+
+uint_t aubio_timestretch_set_transpose (aubio_timestretch_t * o UNUSED, smpl_t transpose UNUSED) {
+  return AUBIO_FAIL;
+}
+
+smpl_t aubio_timestretch_get_transpose (aubio_timestretch_t * o UNUSED) {
+  return 0.;
+}
+
+uint_t aubio_timestretch_get_latency (aubio_timestretch_t * o UNUSED) {
+  return 0.;
+}
+
+uint_t aubio_timestretch_reset(aubio_timestretch_t *o UNUSED) {
+  return AUBIO_FAIL;
+}
+
+sint_t aubio_timestretch_push(aubio_timestretch_t * o UNUSED, fvec_t * in
+    UNUSED, uint_t length UNUSED) {
+  return AUBIO_FAIL;
+}
+
+sint_t aubio_timestretch_get_available(aubio_timestretch_t * o UNUSED) {
+  return AUBIO_FAIL;
+}
+// end of dummy implementation
+
+#endif /* HAVE_RUBBERBAND */
Index: src/effects/timestretch_rubberband.c
===================================================================
--- src/effects/timestretch_rubberband.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/effects/timestretch_rubberband.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,244 @@
+/*
+  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
+
+  This file is part of aubio.
+
+  aubio is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  aubio is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "config.h"
+
+#ifdef HAVE_RUBBERBAND
+
+#include "aubio_priv.h"
+#include "fvec.h"
+#include "fmat.h"
+#include "io/source.h"
+#include "effects/timestretch.h"
+
+#include "rubberband/rubberband-c.h"
+
+#define MIN_STRETCH_RATIO 0.025
+#define MAX_STRETCH_RATIO 40.
+
+#define HAVE_THREADS 1
+#if 0
+#undef HAVE_THREADS
+#endif
+
+#ifdef HAVE_THREADS
+#include <pthread.h>
+#endif
+
+/** generic time stretching structure */
+struct _aubio_timestretch_t
+{
+  uint_t samplerate;              /**< samplerate */
+  uint_t hopsize;                 /**< hop size */
+  smpl_t stretchratio;            /**< time ratio */
+  smpl_t pitchscale;              /**< pitch scale */
+
+  RubberBandState rb;
+  RubberBandOptions rboptions;
+};
+
+extern RubberBandOptions aubio_get_rubberband_opts(const char_t *mode);
+
+static void aubio_timestretch_warmup (aubio_timestretch_t * p);
+
+aubio_timestretch_t *
+new_aubio_timestretch (const char_t * mode, smpl_t stretchratio, uint_t hopsize,
+    uint_t samplerate)
+{
+  aubio_timestretch_t *p = AUBIO_NEW (aubio_timestretch_t);
+  p->hopsize = hopsize;
+  p->pitchscale = 1.;
+
+  if (stretchratio <= MAX_STRETCH_RATIO && stretchratio >= MIN_STRETCH_RATIO) {
+    p->stretchratio = stretchratio;
+  } else {
+    AUBIO_ERR("timestretch: stretchratio should be in the range [%.3f, %.3f], got %f\n",
+        MIN_STRETCH_RATIO, MAX_STRETCH_RATIO, stretchratio);
+    goto beach;
+  }
+
+  p->rboptions = aubio_get_rubberband_opts(mode);
+  if (p->rboptions < 0) {
+    AUBIO_ERR("timestretch: unknown time stretching method %s\n", mode);
+    goto beach;
+  }
+
+  p->rb = rubberband_new(samplerate, 1, p->rboptions, p->stretchratio, p->pitchscale);
+  if (!p->rb) goto beach;
+
+  p->samplerate = samplerate;
+
+  //aubio_timestretch_warmup(p);
+
+  return p;
+
+beach:
+  del_aubio_timestretch(p);
+  return NULL;
+}
+
+static void
+aubio_timestretch_warmup (aubio_timestretch_t * p)
+{
+  // warm up rubber band
+  //AUBIO_WRN("timestretch: warming-up\n");
+  unsigned int latency = MAX(p->hopsize, rubberband_get_latency(p->rb));
+  fvec_t *input = new_fvec(p->hopsize);
+  while (aubio_timestretch_push(p, input, input->length) < (int)latency) {
+    //sint_t available = aubio_timestretch_get_available(p);
+    //AUBIO_WRN("timestretch: warmup got %d, latency: %d\n", available, latency);
+  }
+  del_fvec(input);
+}
+
+void
+del_aubio_timestretch (aubio_timestretch_t * p)
+{
+  if (p->rb) {
+    rubberband_delete(p->rb);
+  }
+  AUBIO_FREE (p);
+}
+
+uint_t
+aubio_timestretch_get_samplerate (aubio_timestretch_t * p)
+{
+  return p->samplerate;
+}
+
+uint_t aubio_timestretch_get_latency (aubio_timestretch_t * p) {
+  return rubberband_get_latency(p->rb);
+}
+
+uint_t
+aubio_timestretch_set_stretch (aubio_timestretch_t * p, smpl_t stretch)
+{
+  if (!p->rb) {
+    AUBIO_WRN("timestretch: could not set stretch ratio, rubberband not created\n");
+    return AUBIO_FAIL;
+  }
+  if (stretch >= MIN_STRETCH_RATIO && stretch <= MAX_STRETCH_RATIO) {
+    p->stretchratio = stretch;
+    rubberband_set_time_ratio(p->rb, 1./p->stretchratio);
+    return AUBIO_OK;
+  } else {
+    AUBIO_WRN("timestretch: could not set stretch ratio to %.2f\n", stretch);
+    return AUBIO_FAIL;
+  }
+}
+
+smpl_t
+aubio_timestretch_get_stretch (aubio_timestretch_t * p)
+{
+  return p->stretchratio;
+}
+
+uint_t
+aubio_timestretch_set_pitchscale (aubio_timestretch_t * p, smpl_t pitchscale)
+{
+  if (!p->rb) {
+    AUBIO_WRN("timestretch: could not set pitch scale, rubberband not created\n");
+    return AUBIO_FAIL;
+  }
+  if (pitchscale >= 0.0625  && pitchscale <= 4.) {
+    p->pitchscale = pitchscale;
+    rubberband_set_pitch_scale(p->rb, p->pitchscale);
+    return AUBIO_OK;
+  } else {
+    AUBIO_WRN("timestretch: could not set pitchscale to %.2f\n", pitchscale);
+    return AUBIO_FAIL;
+  }
+}
+
+smpl_t
+aubio_timestretch_get_pitchscale (aubio_timestretch_t * p)
+{
+  return p->pitchscale;
+}
+
+uint_t
+aubio_timestretch_set_transpose(aubio_timestretch_t * p, smpl_t transpose)
+{
+  if (transpose >= -24. && transpose <= 24.) {
+    smpl_t pitchscale = POW(2., transpose / 12.);
+    return aubio_timestretch_set_pitchscale(p, pitchscale);
+  } else {
+    AUBIO_WRN("timestretch: could not set transpose to %.2f\n", transpose);
+    return AUBIO_FAIL;
+  }
+}
+
+smpl_t
+aubio_timestretch_get_transpose(aubio_timestretch_t * p)
+{
+  return 12. * LOG(p->pitchscale) / LOG(2.0);
+}
+
+sint_t
+aubio_timestretch_push(aubio_timestretch_t *p, fvec_t *input, uint_t length)
+{
+  // push new samples to rubberband, return available
+  int available;
+  int eof = (input->length != length) ? 1 : 0;
+  rubberband_process(p->rb, (const float* const*)&(input->data), length, eof);
+  available = rubberband_available(p->rb);
+  //AUBIO_WRN("timestretch: processed %d, %d available, eof: %d\n",
+  //    length, available, eof);
+  return available;
+}
+
+sint_t
+aubio_timestretch_get_available(aubio_timestretch_t *p) {
+  return rubberband_available(p->rb);
+}
+
+void
+aubio_timestretch_do(aubio_timestretch_t * p, fvec_t * out, uint_t * read)
+{
+  // now retrieve the samples and write them into out->data
+  int available = rubberband_available(p->rb);
+  if (available >= (int)out->length) {
+    rubberband_retrieve(p->rb, (float* const*)&(out->data), out->length);
+    *read = out->length;
+  } else if (available > 0) {
+    // this occurs each time the end of file is reached
+    //AUBIO_WRN("timestretch: short read\n");
+    rubberband_retrieve(p->rb, (float* const*)&(out->data), available);
+    fvec_t zeros; zeros.length = out->length - available; zeros.data = out->data + available;
+    fvec_zeros(&zeros);
+    *read = available;
+  } else {
+    // this may occur if the previous was a short read available == hopsize
+    fvec_zeros(out);
+    *read = 0;
+  }
+}
+
+uint_t
+aubio_timestretch_reset(aubio_timestretch_t *p)
+{
+  uint_t err = AUBIO_OK;
+  if (p->rb) {
+    rubberband_reset(p->rb);
+  }
+  return err;
+}
+
+#endif
Index: src/synth/sampler.c
===================================================================
--- src/synth/sampler.c	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ src/synth/sampler.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -19,4 +19,5 @@
 */
 
+#include <assert.h>
 
 #include "aubio_priv.h"
@@ -24,17 +25,93 @@
 #include "fmat.h"
 #include "io/source.h"
+#include "utils/ringbuffer.h"
 #include "synth/sampler.h"
+
+#define HAVE_THREADS 1
+#define READER_THREAD_ON 1
+#if 0
+#undef HAVE_THREADS
+#endif
+
+#ifdef HAVE_THREADS
+#include <pthread.h>
+#endif
+
+typedef enum {
+  aubio_sampler_reading_from_source,
+  aubio_sampler_reading_from_table,
+  aubio_sampler_n_reading_methods
+} aubio_sampler_reading_method;
+
+
+typedef enum {
+  aubio_sampler_interp_pitchtime,
+  aubio_sampler_interp_quad,
+  aubio_sampler_interp_lin,
+  aubio_sampler_n_interp_methods
+} aubio_sampler_interp_method;
 
 struct _aubio_sampler_t {
   uint_t samplerate;
   uint_t blocksize;
+  // current reading mode (can be a file or an array)
+  uint_t reading_from;
+  // current interpolation mode (can be quadratic, timestretch, ...)
+  uint_t interp;
+  aubio_ringbuffer_t *ring;
+  uint_t perfectloop;
+  uint_t eof_remaining;
+  // reading from a table
+  fvec_t *table;
+  uint_t table_index;
+  // reading from a source
   aubio_source_t *source;
+  const char_t *uri;
+  uint_t playing;
+  uint_t opened;
+  uint_t loop;
+  uint_t finished;              // end of file was reached
+  uint_t eof;                   // end of file is now
+#ifdef HAVE_THREADS
+  // file reading thread
+  pthread_t read_thread;
+  uint_t threaded_read;         // use reading thread?
+  pthread_mutex_t read_mutex;
+  pthread_cond_t read_avail;
+  pthread_cond_t read_request;
+  uint_t source_blocksize;
   fvec_t *source_output;
-  fmat_t *source_output_multi;
-  char_t *uri;
-  uint_t playing;
+  fmat_t *source_moutput;
+  uint_t channels;
+  // file opening thread
+  pthread_t open_thread;
+  pthread_mutex_t open_mutex;
+  uint_t waited;                // number of frames skipped while opening
+  const char_t *next_uri;
+  uint_t open_thread_running;
+  sint_t available;             // number of samples currently available
+  uint_t started;               // source warmed up
+  uint_t read_thread_finish;    // flag to tell reading thread to exit
+#endif
 };
 
-aubio_sampler_t *new_aubio_sampler(uint_t samplerate, uint_t blocksize)
+static sint_t aubio_sampler_pull_from_source(aubio_sampler_t *s);
+
+static void aubio_sampler_do_eof(aubio_sampler_t *s);
+
+static void aubio_sampler_read(aubio_sampler_t *s, fvec_t *output, uint_t *read);
+static void aubio_sampler_read_from_source(aubio_sampler_t *s, fvec_t *output, uint_t *read);
+static void aubio_sampler_read_from_table(aubio_sampler_t *s, fvec_t *output, uint_t *read);
+
+#ifdef HAVE_THREADS
+static void *aubio_sampler_openfn(void *p);
+static void *aubio_sampler_readfn(void *p);
+static void aubio_sampler_open_opening_thread(aubio_sampler_t *o);
+static void aubio_sampler_open_reading_thread(aubio_sampler_t *o);
+static void aubio_sampler_close_opening_thread(aubio_sampler_t *o);
+static void aubio_sampler_close_reading_thread(aubio_sampler_t *o);
+#endif
+
+aubio_sampler_t *new_aubio_sampler(uint_t blocksize, uint_t samplerate)
 {
   aubio_sampler_t *s = AUBIO_NEW(aubio_sampler_t);
@@ -45,8 +122,61 @@
   s->samplerate = samplerate;
   s->blocksize = blocksize;
-  s->source_output = new_fvec(blocksize);
-  s->source_output_multi = new_fmat(4, blocksize);
   s->source = NULL;
   s->playing = 0;
+  s->loop = 0;
+  s->uri = NULL;
+  s->finished = 1;
+  s->eof = 0;
+  s->opened = 0;
+  s->available = 0;
+
+  s->threaded_read = 0;
+  s->perfectloop = 0;
+#if 0 // naive mode
+  s->source_blocksize = s->blocksize;
+#elif 0 // threaded mode, no ringbuffer
+  s->source_blocksize = s->blocksize;
+  s->threaded_read = 1;
+#elif 0 // unthreaded, with ringbuffer
+  s->source_blocksize = 2048; //32 * s->blocksize;
+  s->perfectloop = 1;
+#elif 1 // threaded with ringhbuffer
+  s->source_blocksize = 2048; //32 * s->blocksize;
+  s->perfectloop = 1;
+  s->threaded_read = 1;
+#endif
+
+  if (s->source_blocksize < s->blocksize) {
+    s->source_blocksize = s->blocksize;
+  }
+  // FIXME: perfectloop fails if source_blocksize > 2048 with source_avcodec
+  //s->source_blocksize = 8192;
+
+  if (s->perfectloop || s->source_blocksize != s->blocksize) {
+    s->ring = new_aubio_ringbuffer(s->source_blocksize * 2, s->blocksize);
+  }
+  if (s->threaded_read || s->perfectloop || s->ring)
+    s->source_output = new_fvec(s->source_blocksize);
+  //s->channels = 1;
+  //s->source_moutput = new_fmat(s->source_blocksize, s->channels);
+
+#ifdef HAVE_THREADS
+  aubio_sampler_open_opening_thread(s);
+
+  if (s->threaded_read) {
+    //AUBIO_WRN("sampler: starting reading thread\n");
+    aubio_sampler_open_reading_thread(s);
+  }
+#endif
+
+#if 0
+  s->reading_from = aubio_sampler_reading_from_table;
+  s->perfectloop = 1;
+  s->threaded_read = 0;
+  s->opened = 1;
+  s->finished = 1;
+  s->table_index = 0;
+#endif
+
   return s;
 beach:
@@ -55,53 +185,544 @@
 }
 
+#ifdef HAVE_THREADS
+void aubio_sampler_open_opening_thread(aubio_sampler_t *s) {
+  pthread_mutex_init(&s->open_mutex, 0);
+  s->waited = 0;
+  s->open_thread = 0;
+  s->open_thread_running = 0;
+}
+
+void aubio_sampler_open_reading_thread(aubio_sampler_t *s) {
+  s->read_thread_finish = 0;
+  pthread_mutex_init(&s->read_mutex, 0);
+  pthread_cond_init (&s->read_avail, 0);
+  pthread_cond_init (&s->read_request, 0);
+  pthread_create(&s->read_thread, 0, aubio_sampler_readfn, s);
+}
+
+void aubio_sampler_close_opening_thread(aubio_sampler_t *o) {
+  // clean up opening thread
+  void *threadret;
+  if (!o->open_thread) return;
+  pthread_mutex_destroy(&o->open_mutex);
+  if (o->open_thread_running) {
+    if (pthread_cancel(o->open_thread)) {
+      AUBIO_WRN("sampler: cancelling file opening thread failed\n");
+    }
+  }
+  if (o->open_thread && pthread_join(o->open_thread, &threadret)) {
+    AUBIO_WRN("sampler: joining file opening thread failed\n");
+  }
+  pthread_mutex_destroy(&o->open_mutex);
+  o->open_thread = 0;
+}
+
+void aubio_sampler_close_reading_thread(aubio_sampler_t *o) {
+  // clean up reading thread
+  void *threadret;
+  if (!o->read_thread) return;
+  o->read_thread_finish = 1;
+  pthread_cond_signal(&o->read_request);
+  if (pthread_cancel(o->read_thread)) {
+    AUBIO_WRN("sampler: cancelling file reading thread failed\n");
+  }
+  if (pthread_join(o->read_thread, &threadret)) {
+    AUBIO_WRN("sampler: joining file reading thread failed\n");
+  }
+  pthread_mutex_destroy(&o->read_mutex);
+  pthread_cond_destroy(&o->read_avail);
+  pthread_cond_destroy(&o->read_request);
+  o->read_thread = 0;
+}
+#endif
+
 uint_t aubio_sampler_load( aubio_sampler_t * o, const char_t * uri )
 {
-  if (o->source) del_aubio_source(o->source);
-
-  if (o->uri) AUBIO_FREE(o->uri);
-  o->uri = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX));
-  strncpy(o->uri, uri, strnlen(uri, PATH_MAX));
-
-  o->source = new_aubio_source(uri, o->samplerate, o->blocksize);
-  if (o->source) return 0;
-  AUBIO_ERR("sampler: failed loading %s", uri);
-  return 1;
-}
-
-void aubio_sampler_do ( aubio_sampler_t * o, const fvec_t * input, fvec_t * output)
-{
-  uint_t read = 0, i;
+  uint_t ret = AUBIO_FAIL;
+  aubio_source_t *oldsource = o->source, *newsource = NULL;
+  newsource = new_aubio_source(uri, o->samplerate, o->source_blocksize);
+  if (newsource) {
+    uint_t duration = aubio_source_get_duration(newsource);
+    if (duration < o->blocksize) {
+      AUBIO_WRN("sampler: %s is %d frames long, but blocksize is %d\n",
+          uri, duration, o->blocksize);
+    }
+    o->source = newsource;
+    if (oldsource) del_aubio_source(oldsource);
+    if (o->samplerate == 0) {
+      o->samplerate = aubio_source_get_samplerate(o->source);
+    }
+    o->uri = uri;
+    o->finished = 0;
+    o->eof = 0;
+    o->eof_remaining = 0;
+    o->opened = 1;
+    ret = AUBIO_OK;
+    AUBIO_WRN("sampler: loaded %s\n", uri);
+    if (o->waited) {
+      AUBIO_WRN("sampler: %.2fms (%d samples) taken to load %s\n", 1000. *
+          o->waited / (smpl_t)o->samplerate, o->waited, o->uri);
+    }
+  } else {
+    o->source = NULL;
+    if (oldsource) del_aubio_source(oldsource);
+    o->playing = 0;
+    o->uri = NULL;
+    o->finished = 1;
+    o->eof = 0;
+    o->eof_remaining = 0;
+    o->opened = 0;
+    AUBIO_WRN("sampler: failed loading %s\n", uri);
+  }
+  if (o->ring) {
+    //AUBIO_WRN("sampler: resetting ringbuffer\n");
+    aubio_ringbuffer_reset(o->ring);
+  }
+  return ret;
+}
+
+#ifdef HAVE_THREADS
+static void *aubio_sampler_openfn(void *z) {
+  aubio_sampler_t *p = z;
+  uint_t err;
+  int oldtype;
+  void *ret;
+  pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
+  pthread_mutex_lock(&p->open_mutex);
+  p->open_thread_running = 1;
+  err = aubio_sampler_load(p, p->next_uri);
+  p->open_thread_running = 0;
+  pthread_mutex_unlock(&p->open_mutex);
+  ret = &err;
+  pthread_exit(ret);
+}
+#endif
+
+uint_t
+aubio_sampler_queue(aubio_sampler_t *o, const char_t *uri)
+{
+#ifdef HAVE_THREADS
+  uint_t ret = AUBIO_OK;
+
+  if (o->reading_from == aubio_sampler_reading_from_table) {
+    o->reading_from = aubio_sampler_reading_from_source;
+    o->opened = 0;
+    o->finished = 1;
+  }
+  /* open uri in open_thread */
+  if (o->open_thread_running) {
+    // cancel previous open_thread
+    if (pthread_cancel(o->open_thread)) {
+      AUBIO_WRN("sampler: failed queuing %s (cancelling existing open thread failed)\n", uri);
+      return AUBIO_FAIL;
+    } else {
+      AUBIO_WRN("sampler: cancelled queuing %s (queuing %s now)\n",
+          o->next_uri, uri);
+    }
+    o->open_thread_running = 0;
+  }
+  void *threadret;
+  if (o->open_thread && pthread_join(o->open_thread, &threadret)) {
+    AUBIO_WRN("sampler: joining thread failed\n");
+  }
+  if (pthread_mutex_trylock(&o->open_mutex)) {
+    AUBIO_WRN("sampler: failed queuing %s (locking failed)\n", uri);
+    ret = AUBIO_FAIL;
+    goto lock_failed;
+  }
+  o->opened = 0; // while opening
+  o->started = 0;
+  o->available = 0;
+  o->next_uri = uri;
+  o->waited = 0;
+  if (pthread_create(&o->open_thread, 0, aubio_sampler_openfn, o) != 0) {
+    AUBIO_ERR("sampler: failed creating opening thread\n");
+    ret = AUBIO_FAIL;
+    goto thread_create_failed;
+  }
+
+thread_create_failed:
+  pthread_mutex_unlock(&o->open_mutex);
+lock_failed:
+  if (ret == AUBIO_OK) {
+    //AUBIO_WRN("sampler: queued %s\n", uri);
+  } else {
+    AUBIO_ERR("sampler: queueing %s failed\n", uri);
+  }
+  return ret;
+#else
+  AUBIO_WRN("sampler: opening %s, not queueing (not compiled with threading)\n", uri);
+  return aubio_sampler_load(o, uri);
+#endif
+}
+
+#ifdef HAVE_THREADS
+
+uint_t aubio_sampler_reading_from_source_ring_fetch(aubio_sampler_t*s);
+
+void *aubio_sampler_readfn(void *z) {
+  aubio_sampler_t *p = z;
+  while(1) {
+    pthread_mutex_lock(&p->read_mutex);
+    if (p->open_thread_running) {
+      //AUBIO_WRN("sampler: readfn(): file is being opened\n");
+      pthread_cond_signal(&p->read_avail);
+      //pthread_cond_wait(&p->read_request, &p->read_mutex);
+    } else if (p->opened && !p->started && !p->finished) {
+      //AUBIO_WRN("sampler: readfn(): file started\n");
+      if (p->ring) {
+        p->available = aubio_sampler_reading_from_source_ring_fetch(p);
+      } else {
+        p->available = aubio_sampler_pull_from_source(p);
+        if (p->available < (sint_t)p->source_blocksize)
+          aubio_sampler_do_eof(p);
+      }
+      pthread_cond_signal(&p->read_avail);
+      if (!p->finished) {
+        pthread_cond_wait(&p->read_request, &p->read_mutex);
+      }
+    } else {
+      //AUBIO_WRN("sampler: readfn(): idle?\n");
+      pthread_cond_signal(&p->read_avail);
+      pthread_cond_wait(&p->read_request, &p->read_mutex);
+      if (p->read_thread_finish) {
+        goto done;
+      }
+    }
+    pthread_mutex_unlock(&p->read_mutex);
+  }
+done:
+  //AUBIO_WRN("sampler: exiting reading thread\n");
+  pthread_mutex_unlock(&p->read_mutex);
+  pthread_exit(NULL);
+}
+#endif
+
+void
+aubio_sampler_read(aubio_sampler_t *s, fvec_t *output, uint_t *read) {
+  if (s->reading_from == aubio_sampler_reading_from_source) {
+    aubio_sampler_read_from_source(s, output, read);
+  } else if (s->reading_from == aubio_sampler_reading_from_table) {
+    aubio_sampler_read_from_table(s, output, read);
+  }
+}
+
+static void
+aubio_sampler_reading_from_source_naive(aubio_sampler_t *s, fvec_t * output,
+    uint_t *read)
+{
+  // directly read from disk
+  //aubio_source_do(s->source, output, read);
+  s->source_output = output;
+  *read = aubio_sampler_pull_from_source(s);
+  if (*read < s->source_blocksize) {
+    //AUBIO_WRN("sampler: calling go_eof in _read_from_source()\n");
+    aubio_sampler_do_eof(s);
+  }
+}
+
+uint_t
+aubio_sampler_reading_from_source_ring_fetch(aubio_sampler_t*s) {
+  // read source_blocksize (> blocksize) at once
+  int ring_avail = aubio_ringbuffer_get_available(s->ring);
+  //if (ring_avail < s->blocksize) {
+  uint_t available = 0;
+  if (ring_avail < (sint_t)s->blocksize) {
+    available = aubio_sampler_pull_from_source(s);
+    if (available > 0) {
+      aubio_ringbuffer_push(s->ring, s->source_output, available);
+    }
+    if (available < s->source_blocksize) {
+      if (ring_avail + available <= s->blocksize) {
+        s->eof_remaining = available + ring_avail;
+        if (s->eof_remaining == 0) s->eof_remaining = s->blocksize;
+        //AUBIO_ERR("sampler: marking special eof got: %d, in ring: %d, %d, eof remaining %d\n",
+        //    available, ring_avail, s->blocksize, s->eof_remaining);
+        if (s->loop) {
+          aubio_sampler_seek(s,0);
+          // read some frames from beginning of source for perfect looping
+          if (s->perfectloop) {
+            available = aubio_sampler_pull_from_source(s);
+            if (available <= 0) {
+              AUBIO_ERR("sampler: perfectloop but s->available = 0 !\n");
+            } else {
+              aubio_ringbuffer_push(s->ring, s->source_output, available);
+            }
+          }
+        }
+      }
+    }
+  }
+  return available;
+}
+
+static void
+aubio_sampler_reading_from_source_ring_pull(aubio_sampler_t *s, fvec_t *output,
+    uint_t *read)
+{
+  // write into output
+  int ring_avail = aubio_ringbuffer_get_available(s->ring);
+  if (ring_avail >= (sint_t)s->blocksize) {
+    //AUBIO_MSG("sampler: pulling %d / %d from ringbuffer\n", s->blocksize, ring_avail);
+    aubio_ringbuffer_pull(s->ring, output, s->blocksize);
+    *read = s->blocksize;
+    if (s->eof_remaining > 0) {
+      if (s->eof_remaining <= s->blocksize) {
+        //AUBIO_WRN("sampler: signaling eof\n");
+        s->eof = 1; // signal eof
+        s->eof_remaining = 0;
+      } else if (s->eof_remaining <= s->source_blocksize) {
+        s->eof_remaining -= s->blocksize;
+      }
+    }
+  } else {
+    //AUBIO_MSG("sampler: last frame, pulling remaining %d left\n", ring_avail);
+    *read = 0;
+    if (ring_avail > 0) {
+      // pull remaining frames in ring buffer
+      aubio_ringbuffer_pull(s->ring, output, ring_avail);
+      *read += ring_avail;
+    }
+    // signal eof
+    aubio_sampler_do_eof(s);
+    // finished playing, reset ringbuffer for next read
+    if (!s->playing)
+      aubio_ringbuffer_reset(s->ring);
+  }
+}
+
+static void
+aubio_sampler_reading_from_source_ring(aubio_sampler_t *s, fvec_t *output,
+    uint_t *read)
+{
+  aubio_sampler_reading_from_source_ring_fetch(s);
+  aubio_sampler_reading_from_source_ring_pull(s, output, read);
+}
+
+#ifdef HAVE_THREADS
+static void
+aubio_sampler_read_from_source_threaded(aubio_sampler_t *s, fvec_t *output,
+    uint_t *read) {
+  // request at least output->length
+  // make sure we have enough samples read from source
+  int available;
+  pthread_mutex_lock(&s->read_mutex);
+  if (!s->opened || s->open_thread_running) {
+    //AUBIO_ERR("sampler: _read_from_source: not opened, signaling read_request\n");
+    pthread_cond_signal(&s->read_request);
+    available = 0;
+  } else if (!s->finished) {
+    pthread_cond_signal(&s->read_request);
+    pthread_cond_wait(&s->read_avail, &s->read_mutex);
+    //AUBIO_ERR("sampler: _read_from_source: %d\n", s->available);
+    available = s->available;
+  } else {
+    //AUBIO_WRN("sampler: _read_from_source: eof\n");
+    pthread_cond_signal(&s->read_request);
+    available = 0;
+  }
+  pthread_mutex_unlock(&s->read_mutex);
+  //AUBIO_WRN("sampler: got %d available in _read_from_source\n", available);
+  // read -> number of samples read
+  if (!s->perfectloop && s->source_blocksize == s->blocksize) {
+    if (available >= (sint_t)s->blocksize) {
+      fvec_copy(s->source_output, output);
+      *read = s->blocksize;
+    } else if (available > 0) {
+      fvec_copy(s->source_output, output);
+      *read = available;
+    } else {
+      fvec_zeros(output);
+      *read = 0;
+    }
+  } else {
+    aubio_sampler_reading_from_source_ring_pull(s, output, read);
+  }
+}
+#endif
+
+void
+aubio_sampler_read_from_source(aubio_sampler_t *s, fvec_t *output, uint_t *read) {
+#ifdef HAVE_THREADS
+  if (s->threaded_read) { // if threaded
+    aubio_sampler_read_from_source_threaded(s, output, read);
+  } else
+#endif
+  {
+    if (s->finished) {
+      *read = 0;
+    }
+    else if (s->source_blocksize == s->blocksize && !s->perfectloop) {
+      aubio_sampler_reading_from_source_naive(s, output, read);
+    } else {
+      aubio_sampler_reading_from_source_ring(s, output, read);
+    }
+#if 1
+    if (s->loop && s->perfectloop && *read != s->blocksize) { // && s->started && !s->finished) {
+      AUBIO_ERR("sampler: perfectloop but read only %d\n", *read);
+    }
+#endif
+  }
+}
+
+void
+aubio_sampler_read_from_table(aubio_sampler_t *s, fvec_t *output, uint_t *read) {
+  *read = 0;
+  if (s->table == NULL) {
+    AUBIO_WRN("sampler: _pull_from_table but table not set %d, %d\n",
+        output->length, *read);
+  } else if (s->playing) {
+    uint_t available = s->table->length - s->table_index;
+    fvec_t tmp;
+    tmp.data = s->table->data + s->table_index;
+    if (available < s->blocksize) {
+      //AUBIO_WRN("sampler: _pull_from_table: table length %d, index: %d, read %d\n",
+      //    s->table->length, s->table_index, *read);
+      tmp.length = available;
+      fvec_t tmpout; tmpout.data = output->data; tmpout.length = available;
+      fvec_copy(&tmp, &tmpout);
+      if (s->loop && s->perfectloop) {
+        uint_t remaining = s->blocksize - available;
+        tmpout.data = output->data + available; tmpout.length = remaining;
+        tmp.data = s->table->data; tmp.length = remaining;
+        fvec_copy(&tmp, &tmpout);
+        s->table_index = remaining;
+        *read = s->blocksize;
+      } else {
+        s->table_index = 0;
+        *read = available;
+      }
+      aubio_sampler_do_eof(s);
+    } else {
+      tmp.length = s->blocksize;
+      fvec_copy(&tmp, output);
+      s->table_index += output->length;
+      *read = s->blocksize;
+    }
+  }
+}
+
+uint_t
+aubio_sampler_set_table(aubio_sampler_t *s, fvec_t *samples) {
+  if (!samples || !s) return AUBIO_FAIL;
+  if (s->reading_from == aubio_sampler_reading_from_source) {
+    //aubio_sampler_close_reading_thread(s);
+  }
+  s->table = samples;
+  //AUBIO_INF("sampler: setting table (%d long)\n", s->table->length);
+  s->table_index = 0;
+  s->reading_from = aubio_sampler_reading_from_table;
+  //s->threaded_read = 0;
+  s->opened = 1;
+  s->finished = 1;
+  return AUBIO_OK;
+}
+
+sint_t
+aubio_sampler_pull_from_source(aubio_sampler_t *s)
+{
+  // pull source_blocksize samples from source, return available frames
+  uint_t source_read = s->source_blocksize;
+  if (s->source == NULL) {
+    AUBIO_ERR("sampler: trying to fetch on NULL source\n");
+    return -1;
+  }
+  aubio_source_do(s->source, s->source_output, &source_read);
+  return source_read;
+}
+
+
+uint_t
+aubio_sampler_get_samplerate (aubio_sampler_t *o)
+{
+  return o->samplerate;
+}
+
+uint_t
+aubio_sampler_get_opened (aubio_sampler_t *o)
+{
+  return o->opened; //== 1 ? AUBIO_OK : AUBIO_FAIL;
+}
+
+uint_t
+aubio_sampler_get_finished(aubio_sampler_t *o)
+{
+  return o->finished;
+}
+
+uint_t
+aubio_sampler_get_eof (aubio_sampler_t *o)
+{
+  return o->eof;
+}
+
+uint_t
+aubio_sampler_get_waited_opening (aubio_sampler_t *o, uint_t waited) {
+#ifdef HAVE_THREADS
   if (o->playing) {
-    aubio_source_do (o->source, o->source_output, &read);
-    for (i = 0; i < output->length; i++) {
-      output->data[i] += o->source_output->data[i];
-    }
-    if (read < o->blocksize) o->playing = 0;
-  }
-  if (input && input != output) {
-    for (i = 0; i < output->length; i++) {
-      output->data[i] += input->data[i];
-    }
-  }
-}
-
-void aubio_sampler_do_multi ( aubio_sampler_t * o, const fmat_t * input, fmat_t * output)
-{
-  uint_t read = 0, i, j;
-  if (o->playing) {
-    aubio_source_do_multi (o->source, o->source_output_multi, &read);
-    for (i = 0; i < output->height; i++) {
-      for (j = 0; j < output->length; j++) {
-        output->data[i][j] += o->source_output_multi->data[i][j];
-      }
-    }
-    if ( read < o->blocksize ) o->playing = 0;
-  }
-  if (input && input != output) {
-    for (i = 0; i < output->height; i++) {
-      for (j = 0; j < output->length; j++) {
-        output->data[i][j] += input->data[i][j];
-      }
-    }
+    if (!o->opened) {
+      o->waited += waited;
+    } else if (o->waited) {
+      //AUBIO_WRN("sampler: waited %d frames (%.2fms) while opening %s\n",
+      //    o->waited, 1000.*o->waited/(smpl_t)o->samplerate, o->uri);
+      uint_t waited = o->waited;
+      o->waited = 0;
+      return waited;
+    }
+  }
+#endif
+  return 0;
+}
+
+uint_t
+aubio_sampler_seek(aubio_sampler_t * o, uint_t pos)
+{
+  //AUBIO_WRN("sampler: seeking to 0\n");
+  uint_t ret = AUBIO_FAIL;
+  o->finished = 0;
+  if (!o->opened) return AUBIO_OK;
+  if (o->source) {
+    ret = aubio_source_seek(o->source, pos);
+  } else if (o->table && (sint_t)pos >= 0 && pos < o->table->length) {
+    o->table_index = pos < o->table->length ? pos : o->table->length - 1;
+    ret = AUBIO_OK;
+  }
+  return ret;
+}
+
+void
+aubio_sampler_do_eof (aubio_sampler_t * o)
+{
+  //AUBIO_MSG("sampler: calling  _do_eof()\n");
+  o->finished = 1;
+  o->eof = 1;
+  if (!o->loop) {
+    o->playing = 0;
+  } else {
+    if (o->reading_from == aubio_sampler_reading_from_source)
+      aubio_sampler_seek(o, 0);
+    //o->finished = 0;
+  }
+}
+
+void aubio_sampler_do ( aubio_sampler_t * o, fvec_t * output, uint_t *read)
+{
+  o->eof = 0;
+  if (o->opened == 1 && o->playing) {
+    aubio_sampler_read(o, output, read);
+  } else {
+    fvec_zeros(output);
+    *read = 0;
+  }
+}
+
+void aubio_sampler_do_multi ( aubio_sampler_t * o, fmat_t * output, uint_t *read)
+{
+  o->eof = 0;
+  if (o->opened == 1 && o->playing) {
+    //aubio_sampler_read_multi(o, output, read);
+  } else {
+    fmat_zeros(output);
+    *read = 0;
   }
 }
@@ -118,7 +739,17 @@
 }
 
+uint_t aubio_sampler_get_loop ( aubio_sampler_t * o )
+{
+  return o->loop;
+}
+
+uint_t aubio_sampler_set_loop ( aubio_sampler_t * o, uint_t loop )
+{
+  o->loop = (loop == 1) ? 1 : 0;
+  return 0;
+}
+
 uint_t aubio_sampler_play ( aubio_sampler_t * o )
 {
-  aubio_source_seek (o->source, 0);
   return aubio_sampler_set_playing (o, 1);
 }
@@ -129,12 +760,51 @@
 }
 
+uint_t aubio_sampler_loop ( aubio_sampler_t * o )
+{
+  aubio_sampler_set_loop(o, 1);
+  aubio_sampler_seek(o, 0);
+  return aubio_sampler_set_playing (o, 1);
+}
+
+uint_t aubio_sampler_trigger ( aubio_sampler_t * o )
+{
+  if (o->ring) aubio_ringbuffer_reset(o->ring);
+  aubio_sampler_set_loop(o, 0);
+  aubio_sampler_seek(o, 0);
+  return aubio_sampler_set_playing (o, 1);
+}
+
+uint_t aubio_sampler_set_perfectloop (aubio_sampler_t *s, uint_t perfectloop) {
+  if (!s) return AUBIO_FAIL;
+  s->perfectloop = perfectloop;
+  return AUBIO_OK;
+}
+
+uint_t aubio_sampler_get_perfectloop (aubio_sampler_t *s) {
+  if (!s) return AUBIO_FAIL;
+  return s->perfectloop;
+}
+
 void del_aubio_sampler( aubio_sampler_t * o )
 {
+#ifdef HAVE_THREADS
+  // close opening thread
+  aubio_sampler_close_opening_thread(o);
+  // close reading thread
+  aubio_sampler_close_reading_thread(o);
+#endif
+  //if (o->source_output) {
+  if (o->source_output && (o->threaded_read || o->perfectloop)) {
+    del_fvec(o->source_output);
+  }
+  if (o->source_moutput) {
+    del_fmat(o->source_moutput);
+  }
+  if (o->ring) {
+    del_aubio_ringbuffer(o->ring);
+  }
   if (o->source) {
     del_aubio_source(o->source);
   }
-  if (o->uri) AUBIO_FREE(o->uri);
-  del_fvec(o->source_output);
-  del_fmat(o->source_output_multi);
   AUBIO_FREE(o);
 }
Index: src/synth/sampler.h
===================================================================
--- src/synth/sampler.h	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ src/synth/sampler.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -24,5 +24,5 @@
 /** \file
 
-  Load and play sound files.
+  Load and play a sound file.
 
   This file loads a sample and gets ready to play it.
@@ -30,4 +30,9 @@
   The `_do` function adds the new samples to the input, and write the result as
   the output.
+
+TODO:
+  - add _preset_threaded(level)
+  - add _set_stretch
+  - add _set_pitch
 
   \example synth/test-sampler.c
@@ -50,5 +55,5 @@
 
 */
-aubio_sampler_t * new_aubio_sampler(uint_t samplerate, uint_t hop_size);
+aubio_sampler_t * new_aubio_sampler(uint_t hop_size, uint_t samplerate);
 
 /** load source in sampler
@@ -59,34 +64,55 @@
   \return 0 if successful, non-zero otherwise
 
+  This function attempts to load a new source, swaps the current one with the
+  newly loaded one (or NULL if loading failed), then delete the old one.
+
 */
 uint_t aubio_sampler_load( aubio_sampler_t * o, const char_t * uri );
 
+/** queue source in sampler
+
+  \param o sampler, created by new_aubio_sampler()
+  \param uri the uri of the source to load
+
+  \return 0 if successfully queued, non-zero otherwise
+
+  This function is identical to aubio_sampler_load(), except it will be called
+  in its own thread to avoid blocking calls to aubio_sampler_do().
+
+*/
+uint_t aubio_sampler_queue(aubio_sampler_t * o, const char_t * uri );
+
+/** set array to read from
+
+  \param o sampler, created by new_aubio_sampler()
+  \param samples the vector to set the table to
+
+  \return 0 if successfully set, non-zero otherwise
+
+*/
+uint_t aubio_sampler_set_table(aubio_sampler_t *o, fvec_t *samples);
+
 /** process sampler function
 
   \param o sampler, created by new_aubio_sampler()
-  \param input input of the sampler, to be added to the output
   \param output output of the sampler
-
-This function adds the new samples from the playing source to the output.
-
-If `input` is not NULL and different from `output`, then the samples from `input`
-are added to the output.
-
-*/
-void aubio_sampler_do ( aubio_sampler_t * o, const fvec_t * input, fvec_t * output);
+  \param read number of samples actually read
+
+  This function get new samples from the sampler and store them into output.
+
+  The output vector will be completed with 0 if too few samples are available.
+
+*/
+void aubio_sampler_do ( aubio_sampler_t * o, fvec_t * output, uint_t *read);
 
 /** process sampler function, multiple channels
 
   \param o sampler, created by new_aubio_sampler()
-  \param input input of the sampler, to be added to the output
   \param output output of the sampler
 
-This function adds the new samples from the playing source to the output.
-
-If `input` is not NULL and different from `output`, then the samples from `input`
-are added to the output.
-
-*/
-void aubio_sampler_do_multi ( aubio_sampler_t * o, const fmat_t * input, fmat_t * output);
+  This function is indentical to aubio_sampler_do(), but for a multi-channel source.
+
+*/
+void aubio_sampler_do_multi ( aubio_sampler_t * o, fmat_t * output, uint_t *read);
 
 /** get current playing state
@@ -109,4 +135,16 @@
 uint_t aubio_sampler_set_playing ( aubio_sampler_t * o, uint_t playing );
 
+uint_t aubio_sampler_get_loop(aubio_sampler_t * o);
+
+/** set current looping state
+
+  \param o sampler, created by new_aubio_sampler()
+  \param looping 0 for not looping, 1 for looping
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_set_loop(aubio_sampler_t * o, uint_t loop);
+
 /** play sample from start
 
@@ -118,4 +156,22 @@
 uint_t aubio_sampler_play ( aubio_sampler_t * o );
 
+/** play sample from start, looping it
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_loop ( aubio_sampler_t * o );
+
+/** play sample from start, once
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_trigger ( aubio_sampler_t * o );
+
 /** stop sample
 
@@ -126,4 +182,72 @@
 */
 uint_t aubio_sampler_stop ( aubio_sampler_t * o );
+
+/** get end-of-file status
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 1 when the eof is being reached, 0 otherwise
+
+*/
+uint_t aubio_sampler_get_eof(aubio_sampler_t * o);
+
+/** get end-of-file status
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 1 when the eof is being reached, 0 otherwise
+
+*/
+uint_t aubio_sampler_get_finished (aubio_sampler_t * o);
+
+/** get samplerate
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return samplerate of the sampler
+
+*/
+uint_t aubio_sampler_get_samplerate(aubio_sampler_t * o);
+
+/** get the number of samples that were set to zero while opening a file
+
+  \param o sampler, created by new_aubio_sampler()
+  \param waited the number of frames processed during this block
+
+  \return the total delay in samples when the file was successfuly opened, 0
+  otherwise
+
+*/
+uint_t aubio_sampler_get_waited_opening(aubio_sampler_t * o, uint_t waited);
+
+/** get the current perfect loop mode
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return the total delay in samples when the file was successfuly opened, 0
+  otherwise
+
+*/
+uint_t aubio_sampler_get_perfectloop (aubio_sampler_t *o);
+
+/** set the perfect loop mode
+
+  \param o sampler, created by new_aubio_sampler()
+  \param perfectloop 1 to set perfect loop mode, 0 to turn it of
+
+  \return AUBIO_OK on success, AUBIO_FAIL otherwise
+
+ */
+uint_t aubio_sampler_set_perfectloop (aubio_sampler_t *o, uint_t perfectloop);
+
+/** seek to position
+
+  \param o sampler, created by new_aubio_sampler()
+  \param pos position to seek to, in samples
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_seek(aubio_sampler_t * o, uint_t pos);
 
 /** destroy ::aubio_sampler_t object
Index: src/utils/ringbuffer.c
===================================================================
--- src/utils/ringbuffer.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/utils/ringbuffer.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,168 @@
+
+
+#include "aubio_priv.h"
+#include "fvec.h"
+#include "utils/ringbuffer.h"
+#include <assert.h>
+
+struct _aubio_ringbuffer_t
+{
+  uint_t maxwrite;
+  uint_t maxrequest;
+  fvec_t *buffer;
+  uint_t write_pos;
+  uint_t read_pos;
+  sint_t available;
+};
+
+aubio_ringbuffer_t * new_aubio_ringbuffer(uint_t maxwrite, uint_t maxrequest)
+{
+  aubio_ringbuffer_t *p = AUBIO_NEW (aubio_ringbuffer_t);
+  p->maxwrite = maxwrite;
+  p->maxrequest = maxrequest;
+  //p->buffer = new_fvec(MAX(maxwrite, maxrequest));
+  p->buffer = new_fvec(maxwrite + maxrequest);
+  p->write_pos = p->read_pos = p->available = 0;
+  return p;
+}
+
+uint_t aubio_ringbuffer_push(aubio_ringbuffer_t *o, fvec_t *input, uint_t write) {
+  if ((sint_t)o->buffer->length < o->available + (sint_t)write) {
+    AUBIO_ERR("ringbuffer: push: writing %d but capacity is %d and %d are available\n",
+        write, o->buffer->length, o->available);
+    return AUBIO_FAIL;
+  }
+  if (write > o->maxwrite) {
+    AUBIO_ERR("ringbuffer: push: trying to write %d but maxwrite is %d\n",
+        write, o->maxwrite);
+    return AUBIO_FAIL;
+  } else
+  if (write > input->length) {
+    AUBIO_ERR("ringbuffer: push: trying to write %d, but input vector is %d long\n",
+        write, input->length);
+    return AUBIO_FAIL;
+  } else
+  if (o->write_pos + write <= o->buffer->length) {
+    // write everything at once
+    fvec_t tmp; tmp.data = o->buffer->data + o->write_pos; tmp.length = write;
+    fvec_t tmpin; tmpin.data = input->data; tmpin.length = write;
+    //assert(tmpin.length == tmp.length);
+    fvec_copy(&tmpin, &tmp);
+
+    //AUBIO_WRN("ringbuffer: push1: changing write_pos from %d\n", o->write_pos);
+    o->write_pos = (o->write_pos % o->buffer->length);
+    o->write_pos += write;
+    o->available += write;
+    //AUBIO_WRN("ringbuffer: push1: changed write_pos to %d\n", o->write_pos);
+
+    //AUBIO_WRN("ringbuffer: push1: wrote %d, %d available\n", write,
+    //    o->available);
+
+    return AUBIO_OK;
+  } else {
+    // write in two folds
+    uint_t remaining = o->buffer->length - o->write_pos;
+    fvec_t tmp, tmpin;
+    // write end
+    if (remaining) {
+      tmp.data = o->buffer->data + o->write_pos;
+      tmp.length = remaining;
+      tmpin.data = input->data;
+      tmpin.length = remaining;
+      //assert(tmpin.length == tmp.length);
+      fvec_copy(&tmpin, &tmp);
+    }
+    // write start
+    tmp.data = o->buffer->data;
+    tmp.length = write - remaining;
+    tmpin.data = input->data + remaining;
+    tmpin.length = write - remaining;
+    //assert(tmpin.length == tmp.length);
+    fvec_copy(&tmpin, &tmp);
+    //AUBIO_WRN("ringbuffer: push2: changing write_pos from %d\n", o->write_pos);
+    o->write_pos += write;
+    o->write_pos = (o->write_pos % o->buffer->length);
+    o->available += write;
+    //AUBIO_WRN("ringbuffer: push2: changed write_pos to %d\n", o->write_pos);
+
+    //AUBIO_WRN("ringbuffer: push2: wrote %d, %d available\n", write,
+    //    o->available);
+
+    return AUBIO_OK;
+  }
+}
+
+uint_t aubio_ringbuffer_pull(aubio_ringbuffer_t *o, fvec_t *output, uint_t request) {
+  if (o->available < (sint_t)request) {
+    AUBIO_ERR("ringbuffer: pull: requested %d but %d available\n",
+        request, o->available);
+    return AUBIO_FAIL;
+  }
+  if (request > o->maxrequest) {
+    AUBIO_ERR("ringbuffer: pull: trying to request %d but maxrequest is %d\n",
+        request, o->maxrequest);
+    return AUBIO_FAIL;
+  } else
+  if (request > output->length) {
+    AUBIO_ERR("ringbuffer: pull: trying to request %d, but output vector is %d long\n",
+        request, output->length);
+    return AUBIO_FAIL;
+  } else
+  if (o->read_pos + request <= o->buffer->length) {
+    // read everything at once
+    fvec_t tmp; tmp.data = o->buffer->data + o->read_pos; tmp.length = request;
+    fvec_t tmpout; tmpout.data = output->data; tmpout.length = request;
+    //assert(tmpout.length == tmp.length);
+    fvec_copy(&tmp, &tmpout);
+    //AUBIO_WRN("ringbuffer: pull1: changing read_pos from %d\n", o->read_pos);
+    o->read_pos += request;
+    o->read_pos %= o->buffer->length;
+    o->available -= request;
+    //AUBIO_WRN("ringbuffer: pull1: read %d, %d available\n", request,
+    //    o->available);
+    //AUBIO_WRN("ringbuffer: pull1: changed read_pos to %d\n", o->read_pos);
+    return AUBIO_OK;
+  } else {
+    // read in two folds
+    uint_t remaining = o->buffer->length - o->read_pos;
+    fvec_t tmp, tmpout;
+    tmp.data = o->buffer->data + o->read_pos;
+    tmp.length = remaining;
+    tmpout.data = output->data;
+    tmpout.length = remaining;
+    //assert(tmpout.length == tmp.length);
+    fvec_copy(&tmpout, &tmp);
+    // write start
+    tmp.data = o->buffer->data;
+    tmp.length = request - remaining;
+    tmpout.data = output->data + remaining;
+    tmpout.length = request - remaining;
+    //assert(tmpout.length == tmp.length);
+    fvec_copy(&tmp, &tmpout);
+    //AUBIO_WRN("ringbuffer: pull2: changing read_pos from %d\n", o->read_pos);
+    o->read_pos += request;
+    o->read_pos %= o->buffer->length;
+    o->available -= request;
+    //AUBIO_WRN("ringbuffer: pull2: changed read_pos to %d\n", o->read_pos);
+    //AUBIO_WRN("ringbuffer: pull2: read %d, %d available\n", request,
+    //    o->available);
+    return AUBIO_OK;
+  }
+}
+
+uint_t aubio_ringbuffer_reset(aubio_ringbuffer_t *o) {
+  o->read_pos = o->write_pos = 0;
+  o->available = 0;
+  return AUBIO_OK;
+}
+
+sint_t aubio_ringbuffer_get_available(aubio_ringbuffer_t *o) {
+  //AUBIO_WRN("ringbuffer: got %d available (%d ... %d)\n",
+  //    o->available, o->read_pos, o->write_pos);
+  return o->available;
+}
+
+void del_aubio_ringbuffer(aubio_ringbuffer_t *o) {
+  del_fvec(o->buffer);
+  AUBIO_FREE(o);
+}
Index: src/utils/ringbuffer.h
===================================================================
--- src/utils/ringbuffer.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ src/utils/ringbuffer.h	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,28 @@
+
+
+#ifndef AUBIO_RINGBUFFER_H
+#define AUBIO_RINGBUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _aubio_ringbuffer_t aubio_ringbuffer_t;
+
+aubio_ringbuffer_t * new_aubio_ringbuffer(uint_t maxwrite, uint_t maxrequest);
+
+uint_t aubio_ringbuffer_push(aubio_ringbuffer_t *o, fvec_t *output, uint_t write);
+
+uint_t aubio_ringbuffer_pull(aubio_ringbuffer_t *o, fvec_t *input, uint_t request);
+
+sint_t aubio_ringbuffer_get_available(aubio_ringbuffer_t *o);
+
+uint_t aubio_ringbuffer_reset(aubio_ringbuffer_t *o);
+
+void del_aubio_ringbuffer(aubio_ringbuffer_t *o);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AUBIO_RINGBUFFER_H */
Index: src/wscript_build
===================================================================
--- src/wscript_build	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ src/wscript_build	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -6,4 +6,5 @@
 uselib += ['SAMPLERATE']
 uselib += ['SNDFILE']
+uselib += ['RUBBERBAND']
 uselib += ['AVCODEC']
 uselib += ['AVFORMAT']
@@ -11,4 +12,5 @@
 uselib += ['AVUTIL']
 uselib += ['BLAS']
+uselib += ['PTHREAD']
 
 source = ctx.path.ant_glob('*.c **/*.c')
Index: tests/src/effects/test-pitchshift.c
===================================================================
--- tests/src/effects/test-pitchshift.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ tests/src/effects/test-pitchshift.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,75 @@
+#define AUBIO_UNSTABLE 1
+#include <aubio.h>
+#include "utils_tests.h"
+
+int main (int argc, char **argv)
+{
+  sint_t err = 0;
+
+  if (argc < 4) {
+    err = 2;
+    PRINT_ERR("not enough arguments\n");
+    PRINT_MSG("usage: %s <input_path> <output_path> <transpose> [mode] [hop_size] [samplerate]\n", argv[0]);
+    PRINT_MSG(" with <transpose> a number of semi tones in the range [-24, 24]\n");
+    PRINT_MSG(" and [mode] in 'default', 'crispness:0', ..., 'crispness:6'\n");
+    return err;
+  }
+
+  uint_t samplerate = 0;
+  uint_t hop_size = 64;
+  smpl_t transpose = 0.;
+  uint_t n_frames = 0, read = 0;
+
+  char_t *source_path = argv[1];
+  char_t *sink_path = argv[2];
+  char_t *mode = "default";
+
+  transpose = atof(argv[3]);
+
+  if ( argc >= 5 ) mode = argv[4];
+  if ( argc >= 6 ) hop_size = atoi(argv[5]);
+  if ( argc >= 7 ) samplerate = atoi(argv[6]);
+  if ( argc >= 8 ) {
+    err = 2;
+    PRINT_ERR("too many arguments\n");
+    return err;
+  }
+
+  fvec_t *vec = new_fvec(hop_size);
+  fvec_t *out = new_fvec(hop_size);
+  if (!vec) { err = 1; goto beach_fvec; }
+
+  aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size);
+  if (!i) { err = 1; goto beach_source; }
+
+  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i);
+
+  aubio_sink_t *o = new_aubio_sink(sink_path, samplerate);
+  if (!o) { err = 1; goto beach_sink; }
+
+  aubio_pitchshift_t *ps = new_aubio_pitchshift(mode, transpose, hop_size, samplerate);
+  if (!ps) { err = 1; goto beach_pitchshift; }
+
+  do {
+    aubio_source_do(i, vec, &read);
+    //aubio_pitchshift_set_transpose(ps, tranpose);
+    aubio_pitchshift_do(ps, vec, out);
+    aubio_sink_do(o, out, read);
+    n_frames += read;
+  } while ( read == hop_size );
+
+  PRINT_MSG("read %d frames at %dHz (%d blocks) from %s written to %s\n",
+      n_frames, samplerate, n_frames / hop_size,
+      source_path, sink_path);
+
+  del_aubio_pitchshift(ps);
+beach_pitchshift:
+  del_aubio_sink(o);
+beach_sink:
+  del_aubio_source(i);
+beach_source:
+  del_fvec(vec);
+  del_fvec(out);
+beach_fvec:
+  return err;
+}
Index: tests/src/effects/test-timestretch.c
===================================================================
--- tests/src/effects/test-timestretch.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ tests/src/effects/test-timestretch.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,100 @@
+#define AUBIO_UNSTABLE 1
+#include <aubio.h>
+#include "utils_tests.h"
+
+int main (int argc, char **argv)
+{
+  sint_t err = 0;
+
+  if (argc < 4) {
+    err = 2;
+    PRINT_ERR("not enough arguments\n");
+    PRINT_MSG("usage: %s <input_path> <output_path> <stretch> [transpose] [mode] [hop_size] [samplerate]\n", argv[0]);
+    PRINT_MSG(" with <stretch> a time stretching ratio in the range [0.025, 10.]\n");
+    PRINT_MSG("      [transpose] a number of semi tones in the range [-24, 24]\n");
+    PRINT_MSG("  and [mode] in 'default', 'crispness:0', ..., 'crispness:6'\n");
+    return err;
+  }
+
+  uint_t samplerate = 0;
+  uint_t hop_size = 64;
+  smpl_t transpose = 0.;
+  smpl_t stretch = 1.;
+  uint_t n_frames = 0, read = 0;
+  uint_t eof = 0, source_read = 0;
+
+  char_t *source_path = argv[1];
+  char_t *sink_path = argv[2];
+  char_t *mode = "default";
+
+  stretch = atof(argv[3]);
+
+  if ( argc >= 5 ) transpose = atof(argv[4]);
+  if ( argc >= 6 ) mode = argv[5];
+  if ( argc >= 7 ) hop_size = atoi(argv[6]);
+  if ( argc >= 8 ) samplerate = atoi(argv[7]);
+  if ( argc >= 9 ) {
+    err = 2;
+    PRINT_ERR("too many arguments\n");
+    return err;
+  }
+
+  uint_t source_hopsize = 2048;
+  aubio_source_t *s = new_aubio_source(source_path, samplerate, source_hopsize);
+  if (!s) { err = 1; goto beach_source; }
+  if (samplerate == 0) samplerate = aubio_source_get_samplerate(s);
+
+  fvec_t *in = new_fvec(source_hopsize);
+  fvec_t *out = new_fvec(hop_size);
+  if (!out) { err = 1; goto beach_fvec; }
+
+  aubio_timestretch_t *ps = new_aubio_timestretch(mode, stretch, hop_size,
+      samplerate);
+  if (!ps) { err = 1; goto beach_timestretch; }
+  //if (samplerate == 0 ) samplerate = aubio_timestretch_get_samplerate(ps);
+
+  aubio_sink_t *o = new_aubio_sink(sink_path, samplerate);
+  if (!o) { err = 1; goto beach_sink; }
+
+  if (transpose != 0) aubio_timestretch_set_transpose(ps, transpose);
+
+  do {
+    //aubio_timestretch_set_stretch(ps, stretch);
+    //aubio_timestretch_set_transpose(ps, transpose);
+
+    while (aubio_timestretch_get_available(ps) < (sint_t)hop_size && !eof) {
+      aubio_source_do(s, in, &source_read);
+      aubio_timestretch_push(ps, in, source_read);
+      if (source_read < in->length) eof = 1;
+    }
+#if 0
+    if (n_frames == hop_size * 200) {
+      PRINT_MSG("sampler: setting stretch gave %d\n",
+          aubio_timestretch_set_stretch(ps, 2.) );
+      PRINT_MSG("sampler: getting stretch gave %f\n",
+          aubio_timestretch_get_stretch(ps) );
+      PRINT_MSG("sampler: setting transpose gave %d\n",
+          aubio_timestretch_set_transpose(ps, 12.) );
+      PRINT_MSG("sampler: getting transpose gave %f\n",
+          aubio_timestretch_get_transpose(ps) );
+    }
+#endif
+    aubio_timestretch_do(ps, out, &read);
+    aubio_sink_do(o, out, read);
+    n_frames += read;
+  } while ( read == hop_size );
+
+  PRINT_MSG("wrote %d frames at %dHz (%d blocks) from %s written to %s\n",
+      n_frames, samplerate, n_frames / hop_size,
+      source_path, sink_path);
+
+  del_aubio_sink(o);
+beach_sink:
+  del_aubio_timestretch(ps);
+beach_timestretch:
+  del_fvec(out);
+beach_fvec:
+  del_aubio_source(s);
+beach_source:
+  return err;
+}
Index: tests/src/synth/test-sampler.c
===================================================================
--- tests/src/synth/test-sampler.c	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ tests/src/synth/test-sampler.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -1,4 +1,13 @@
 #include <aubio.h>
 #include "utils_tests.h"
+
+int time_was_reached (smpl_t time_s, uint_t n_frames, uint_t hop_size, uint_t samplerate) {
+    if ((n_frames / hop_size) == (uint_t)(time_s * samplerate) / hop_size) {
+      PRINT_MSG("reached %.2f sec at %d samples\n", time_s, n_frames);
+      return 1;
+    } else {
+      return 0;
+    }
+}
 
 int main (int argc, char **argv)
@@ -6,54 +15,114 @@
   sint_t err = 0;
 
-  if (argc < 4) {
+  if (argc < 2) {
     err = 2;
     PRINT_ERR("not enough arguments\n");
-    PRINT_MSG("usage: %s <input_path> <output_path> <sample_path> [samplerate]\n", argv[0]);
+    PRINT_MSG("usage: %s <sample_path> [samplerate] [blocksize] [output_path]\n", argv[0]);
     return err;
   }
 
-  uint_t samplerate = 0; // default is the samplerate of input_path
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
+  uint_t samplerate = 44100; // default is 44100
+  uint_t hop_size = 64; //256;
+  uint_t n_frames = 0, frames_played = 0;
+  uint_t read = 0;
+  char_t *sink_path = NULL;
+  aubio_sink_t *sink = NULL;
 
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
-  char_t *sample_path = argv[3];
-  if ( argc == 5 ) samplerate = atoi(argv[4]);
+  char_t *sample_path = argv[1];
+  if ( argc > 2 ) samplerate = atoi(argv[2]);
+  if ( argc > 3 ) hop_size = atoi(argv[3]);
+  if ( argc > 4 ) sink_path = argv[4];
 
   fvec_t *vec = new_fvec(hop_size);
-  aubio_source_t *source = new_aubio_source(source_path, samplerate, hop_size);
-  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(source);
-  aubio_sink_t *sink = new_aubio_sink(sink_path, samplerate);
 
-  aubio_sampler_t * sampler = new_aubio_sampler (samplerate, hop_size);
+  aubio_sampler_t * sampler = new_aubio_sampler (hop_size, samplerate);
+  if (!vec) goto beach;
+  if (!sampler) goto beach_sampler;
+  // load source file
+  aubio_sampler_load (sampler, sample_path);
+  // load source file (asynchronously)
+  //aubio_sampler_queue (sampler, sample_path);
+  samplerate = aubio_sampler_get_samplerate (sampler);
+  if (samplerate == 0) {
+    PRINT_ERR("starting with samplerate = 0\n");
+    //goto beach_sink;
+  }
 
-  aubio_sampler_load (sampler, sample_path);
+  if (sink_path) {
+    sink = new_aubio_sink(sink_path, samplerate);
+    if (!sink) goto beach_sink;
+  }
+
+  smpl_t sample_duration = 2.953;
+  uint_t sample_repeat = 10;
+  smpl_t t1 = 1.,
+         t2 = t1 + sample_duration * sample_repeat - .1,
+         t3 = t2 - sample_duration + .1,
+         t4 = t3 + sample_duration + .1,
+         t5 = t4 + sample_duration + .1,
+         total_duration = t5 + sample_duration + .1;
+
+  //aubio_sampler_set_transpose(sampler, 0.);
+  //aubio_sampler_set_stretch(sampler, .8);
 
   do {
-    aubio_source_do(source, vec, &read);
-    if (n_frames / hop_size == 10) {
+    if (time_was_reached(t1, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test one shot play of loaded sample\n");
+      aubio_sampler_set_loop( sampler, 1);
       aubio_sampler_play ( sampler );
+    } else if (time_was_reached(t2, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test queueing while playing after eof was reached\n");
+      //aubio_sampler_queue (sampler, sample_path);
+      //aubio_sampler_play (sampler);
+      aubio_sampler_set_loop( sampler, 0);
+#if 0
+    } else if (time_was_reached(t3, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test queueing twice cancels the first one\n");
+      aubio_sampler_queue (sampler, sample_path);
+      aubio_sampler_queue (sampler, sample_path);
+      aubio_sampler_play (sampler);
+    } else if (time_was_reached(t4, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test queueing a corrupt file\n");
+      aubio_sampler_queue (sampler, "/dev/null");
+      aubio_sampler_play (sampler);
+    } else if (time_was_reached(t5, n_frames, hop_size, samplerate)) {
+      aubio_sampler_stop ( sampler );
+      PRINT_MSG("`-test queueing a correct file after a corrupt one\n");
+      uint_t i;
+      for (i = 0; i < 4; i++)
+        aubio_sampler_queue (sampler, "/dev/null");
+      aubio_sampler_queue (sampler, "/dev/null1");
+      aubio_sampler_queue (sampler, "/dev/null2");
+      aubio_sampler_queue (sampler, sample_path);
+      aubio_sampler_play (sampler);
+#endif
     }
+    /*
     if (n_frames / hop_size == 40) {
-      aubio_sampler_play ( sampler );
+      aubio_sampler_queue (sampler, sample_path);
+      aubio_sampler_queue (sampler, sample_path);
+      aubio_sampler_seek ( sampler, 0);
     }
     if (n_frames / hop_size == 70) {
-      aubio_sampler_play ( sampler );
+      aubio_sampler_seek ( sampler, 0);
     }
-    if (n_frames > 10.0 * samplerate) {
-      aubio_sampler_stop ( sampler );
-    }
-    aubio_sampler_do (sampler, vec, vec);
-    aubio_sink_do(sink, vec, read);
-    n_frames += read;
-  } while ( read == hop_size );
+    */
+    aubio_sampler_do (sampler, vec, &read);
+    if (sink) aubio_sink_do(sink, vec, hop_size);
+    n_frames += hop_size;
+    frames_played += read;
+  //} while ( read == hop_size );
+    // last for 40 seconds
+  } while ( n_frames <= total_duration * samplerate );
+  PRINT_MSG("reached %.2f sec at %d samples, sampler played %d frames\n",
+      total_duration, n_frames, frames_played);
 
+  if (sink) del_aubio_sink(sink);
+beach_sink:
   del_aubio_sampler(sampler);
-  del_aubio_source(source);
-  del_aubio_sink(sink);
+beach_sampler:
   del_fvec(vec);
+beach:
   aubio_cleanup();
-
   return 0;
 }
Index: tests/src/synth/test-sampler_table.c
===================================================================
--- tests/src/synth/test-sampler_table.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
+++ tests/src/synth/test-sampler_table.c	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -0,0 +1,99 @@
+#include <aubio.h>
+#include "utils_tests.h"
+
+int time_was_reached (smpl_t time_s, uint_t n_frames, uint_t hop_size, uint_t samplerate) {
+    if ((n_frames / hop_size) == (uint_t)(time_s * samplerate) / hop_size) {
+      PRINT_MSG("reached %.2f sec at %d samples\n", time_s, n_frames);
+      return 1;
+    } else {
+      return 0;
+    }
+}
+
+int main (int argc, char **argv)
+{
+  sint_t err = 0;
+
+  if (argc < 1) {
+    err = 2;
+    PRINT_ERR("not enough arguments\n");
+    PRINT_MSG("usage: %s [samplerate] [blocksize] [output_path]\n", argv[0]);
+    return err;
+  }
+
+  uint_t samplerate = 44100;
+  uint_t hop_size = 64; //256;
+  uint_t n_frames = 0, frames_played = 0;
+  uint_t read = 0;
+  char_t *sink_path = NULL;
+  aubio_sink_t *sink = NULL;
+
+  if ( argc > 1 ) samplerate = atoi(argv[1]);
+  if ( argc > 2 ) hop_size = atoi(argv[2]);
+  if ( argc > 3 ) sink_path = argv[3];
+
+  fvec_t *vec = new_fvec(hop_size);
+
+  aubio_sampler_t * sampler = new_aubio_sampler (hop_size, samplerate);
+  if (!vec) goto beach;
+  if (!sampler) goto beach_sampler;
+  // load source file
+  //aubio_sampler_load (sampler, sample_path);
+  // load source file (asynchronously)
+  //aubio_sampler_queue (sampler, sample_path);
+  samplerate = aubio_sampler_get_samplerate (sampler);
+  if (samplerate == 0) {
+    PRINT_ERR("starting with samplerate = 0\n");
+    //goto beach_sink;
+  }
+
+  //fvec_t *table = new_fvec(1234560);
+  fvec_t *table = new_fvec(123456);
+
+  aubio_sampler_set_table(sampler, table);
+
+  if (sink_path) {
+    sink = new_aubio_sink(sink_path, samplerate);
+    if (!sink) goto beach_sink;
+  }
+
+  smpl_t sample_duration = table->length/(smpl_t) samplerate;
+  uint_t sample_repeat = 10;
+  smpl_t t1 = 1.,
+         t2 = t1 + sample_duration * sample_repeat - .1,
+         t3 = t2 - sample_duration + .1,
+         t4 = t3 + sample_duration + .1,
+         t5 = t4 + sample_duration + .1,
+         total_duration = t5 + sample_duration + .1;
+
+  //aubio_sampler_set_transpose(sampler, 0.);
+  //aubio_sampler_set_stretch(sampler, .8);
+
+  do {
+    if (time_was_reached(t1, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test one shot play of loaded sample\n");
+      aubio_sampler_set_loop( sampler, 1);
+      aubio_sampler_play ( sampler );
+    } else if (time_was_reached(t2, n_frames, hop_size, samplerate)) {
+      PRINT_MSG("`-test queueing while playing after eof was reached\n");
+      //aubio_sampler_queue (sampler, sample_path);
+      //aubio_sampler_play (sampler);
+      aubio_sampler_set_loop( sampler, 0);
+    }
+    aubio_sampler_do (sampler, vec, &read);
+    if (sink) aubio_sink_do(sink, vec, hop_size);
+    n_frames += hop_size;
+    frames_played += read;
+  } while ( n_frames <= total_duration * samplerate );
+  PRINT_MSG("reached %.2f sec at %d samples, sampler played %d frames\n",
+      total_duration, n_frames, frames_played);
+
+  if (sink) del_aubio_sink(sink);
+beach_sink:
+  del_aubio_sampler(sampler);
+beach_sampler:
+  del_fvec(vec);
+beach:
+  aubio_cleanup();
+  return 0;
+}
Index: wscript
===================================================================
--- wscript	(revision 67b66184a3c5ac1bc2d2bc837bc5b1f192f10b15)
+++ wscript	(revision 41b985f1e3468fe8547f44c2ae6b38f2a437a3f3)
@@ -75,4 +75,7 @@
             help_str = 'compile with samplerate (auto)',
             help_disable_str = 'disable samplerate')
+    add_option_enable_disable(ctx, 'rubberband', default = None,
+            help_str = 'compile with rubberband (auto)',
+            help_disable_str = 'disable rubberband')
     add_option_enable_disable(ctx, 'memcpy', default = True,
             help_str = 'use memcpy hacks (default)',
@@ -127,4 +130,9 @@
     ctx.check(header_name='unistd.h', mandatory = False)
 
+    ctx.check(header_name='pthread.h', mandatory = False)
+    needs_pthread = ctx.get_define("HAVE_PTHREAD_H") is not None
+    if needs_pthread:
+        ctx.check_cc(lib="pthread", uselib_store="PTHREAD", mandatory=needs_pthread)
+
     target_platform = sys.platform
     if ctx.options.target_platform:
@@ -307,4 +315,10 @@
                 args = '--cflags --libs',
                 mandatory = ctx.options.enable_samplerate)
+
+    # check for librubberband
+    if (ctx.options.enable_rubberband != False):
+        ctx.check_cfg(package = 'rubberband', atleast_version = '1.3',
+                args = '--cflags --libs',
+                mandatory = ctx.options.enable_rubberband)
 
     # check for jack
