shithub: aubio

Download patch

ref: 05ed7f562616cee876d2e4cd397cdb88f600942f
parent: 7b8e51cdcaeebac5bcb54bd158057b396d580fe3
parent: bd183b3e190524d5fbbba2d8e09995a0fa848e42
author: Paul Brossier <piem@piem.org>
date: Thu Jul 2 15:03:56 EDT 2020

Merge branch 'feature/timestretch'

--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,7 +2,7 @@
   name: Install apt packages
   command: |
     sudo apt-get update
-    sudo apt-get -y install make sox pkg-config libavcodec-dev libavformat-dev libavresample-dev libavutil-dev libsndfile1-dev libsamplerate-dev libvorbis-dev libflac-dev
+    sudo apt-get -y install make sox pkg-config libavcodec-dev libavformat-dev libavresample-dev libavutil-dev libsndfile1-dev libsamplerate-dev librubberband-dev libvorbis-dev libflac-dev
 
 pip-install: &pip-install
   name: Install pip dependencies
--- a/.travis.yml
+++ b/.travis.yml
@@ -75,6 +75,7 @@
     - libjack-dev
     - libasound2-dev
     - libfftw3-dev
+    - librubberband-dev
     - sox
     - lcov
   homebrew:
@@ -84,6 +85,8 @@
     - libsndfile
     - libvorbis
     - flac
+    - libsamplerate
+    - rubberband
     - lcov
     update: true
 
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -6,7 +6,7 @@
     vmImage: 'ubuntu-latest'
   steps:
   - script: |
-      sudo apt install libavformat-dev
+      sudo apt install libavformat-dev librubberband-dev
     displayName: 'deps'
   - script: |
       make
--- /dev/null
+++ b/python/demos/demo_pitchshift.py
@@ -1,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)
--- a/python/lib/gen_code.py
+++ b/python/lib/gen_code.py
@@ -20,6 +20,7 @@
     'ratio': '0.5',
     'method': '"default"',
     'uri': '"none"',
+    'transpose': '0.',
     }
 
 member_types = {
@@ -83,6 +84,7 @@
         'tempo': '1',
         'filterbank': 'self->n_filters',
         'tss': 'self->buf_size',
+        'pitchshift': 'self->hop_size',
         'dct': 'self->size',
         }
 
@@ -96,6 +98,7 @@
         'tempo': 'self->hop_size',
         'wavetable': 'self->hop_size',
         'tss': 'self->buf_size / 2 + 1',
+        'pitchshift': 'self->hop_size',
         }
 
 def get_name(proto):
@@ -290,6 +293,8 @@
             return self.check_valid_uint(p)
         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'])
 
@@ -310,6 +315,15 @@
         return """
     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};
     }}
 """.format(defval = aubiodefvalue[name], name = name)
--- a/python/lib/gen_external.py
+++ b/python/lib/gen_external.py
@@ -46,6 +46,7 @@
     #'sampler',
     'audio_unit',
     'spectral_whitening',
+    'timestretch', # TODO fix parsing of uint_t *read in _do
 ]
 
 
--- a/python/lib/moresetuptools.py
+++ b/python/lib/moresetuptools.py
@@ -78,7 +78,9 @@
     print("Info: looking for *optional* additional packages")
     packages = ['libavcodec', 'libavformat', 'libavutil',
                 'libswresample', 'libavresample',
+                'jack',
                 'sndfile',
+                'rubberband',
                 #'fftw3f',
                ]
     # samplerate only works with float
@@ -100,6 +102,8 @@
         ext.define_macros += [('HAVE_SNDFILE', 1)]
     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)]
         ext.define_macros += [('HAVE_FFTW3', 1)]
--- /dev/null
+++ b/python/tests/test_pitchshift.py
@@ -1,0 +1,104 @@
+#! /usr/bin/env python
+
+from numpy.testing import TestCase
+from _tools import parametrize, skipTest
+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 Test_aubio_pitchshift_testruns(object):
+
+    run_args = ['mode', 'pitchscale', 'hop_size', 'samplerate']
+    run_values = [
+            ("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),
+            ]
+
+    @parametrize(run_args, run_values)
+    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:
+            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 _tools import run_module_suite
+    run_module_suite()
--- a/src/aubio.h
+++ b/src/aubio.h
@@ -220,6 +220,8 @@
 #include "pitch/pitchfcomb.h"
 #include "pitch/pitchspecacf.h"
 #include "tempo/beattracking.h"
+#include "effects/pitchshift.h"
+#include "effects/timestretch.h"
 #include "utils/scale.h"
 #include "utils/hist.h"
 #endif
--- /dev/null
+++ b/src/effects/pitchshift.h
@@ -1,0 +1,127 @@
+/*
+  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
+
+  pitchscale is a frequency ratio. It should be in the range [0.25, 4].
+
+  \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 */
--- /dev/null
+++ b/src/effects/pitchshift_dummy.c
@@ -1,0 +1,73 @@
+/*
+  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 "aubio_priv.h"
+
+#ifndef HAVE_RUBBERBAND
+
+#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 */
--- /dev/null
+++ b/src/effects/pitchshift_rubberband.c
@@ -1,0 +1,162 @@
+/*
+  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 "aubio_priv.h"
+
+#ifdef HAVE_RUBBERBAND
+
+#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.;
+  p->rb = NULL;
+  if ((sint_t)hopsize <= 0) {
+    AUBIO_ERR("pitchshift: hop_size should be >= 0, got %d\n", hopsize);
+    goto beach;
+  }
+  if ((sint_t)samplerate <= 0) {
+    AUBIO_ERR("pitchshift: samplerate should be >= 0, got %d\n", samplerate);
+    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 (aubio_pitchshift_set_transpose(p, transpose)) goto beach;
+
+#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 '%f',"
+        " should be in the range [0.25, 4.].\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 '%f',"
+        " should be in the range [-24; 24.].\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)
+{
+  // third parameter is always 0 since we are never expecting a final frame
+  rubberband_process(p->rb, (const float* const*)&(in->data), p->hopsize, 0);
+  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
--- /dev/null
+++ b/src/effects/rubberband_utils.c
@@ -1,0 +1,150 @@
+
+
+#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
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+char_t** aubio_split_str(const char_t* str, const char_t sep) {
+  char_t** result = 0;
+  uint_t count = 0;
+  char_t input[PATH_MAX];
+  char_t* in_ptr = input;
+  char_t* last_sep = 0;
+  char_t delim[2]; delim[0] = sep; delim[1] = 0;
+
+  strncpy(input, str, PATH_MAX);
+  input[PATH_MAX - 1] = '\0';
+
+  // count number of elements
+  while (*in_ptr) {
+    if (sep == *in_ptr) {
+      count++;
+      last_sep = in_ptr;
+    }
+    in_ptr++;
+  }
+  // add space for trailing token.
+  count += last_sep < (input + strlen(input) - 1);
+  count++;
+
+  result = AUBIO_ARRAY(char_t*, count);
+  if (result) {
+    uint_t idx = 0;
+    char_t* params = strtok(input, delim);
+    while (params) {
+      // make sure we don't got in the wild
+      if (idx >= count)
+        break;
+      *(result + idx++) = strdup(params);
+      params = strtok(0, delim);
+    }
+    // add null string at the end if needed
+    if (idx < count - 1)
+      *(result + idx) = 0;
+  }
+  return result;
+}
+
+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 {
+    // attempt to parse a list of options, separated with ','
+    char_t **params = aubio_split_str(mode, ':');
+    uint_t i = 0;
+    if (!params || !params[0]) {
+      // memory failure occurred or empty string was passed
+      AUBIO_ERR("rubberband_utils: failed parsing options\n");
+      rboptions = -1;
+    }
+    while (*(params + i) != NULL) {
+      if ( strcmp(params[i], "ProcessOffline" ) == 0 )        {
+             rboptions = RubberBandOptionProcessOffline;
+        // TODO: add wrapper to rb study(smpl_t *input, uint_t write)
+        AUBIO_ERR("rubberband_utils: RubberBandOptionProcessOffline is not available\n");
+        rboptions = -1;
+      }
+      else if ( strcmp(params[i], "ProcessRealTime" ) == 0 )       rboptions |= RubberBandOptionProcessRealTime;
+      else if ( strcmp(params[i], "StretchElastic" ) == 0 )        rboptions |= RubberBandOptionStretchElastic;
+      else if ( strcmp(params[i], "StretchPrecise" ) == 0 )        rboptions |= RubberBandOptionStretchPrecise;
+      else if ( strcmp(params[i], "TransientsCrisp" ) == 0 )       rboptions |= RubberBandOptionTransientsCrisp;
+      else if ( strcmp(params[i], "TransientsMixed" ) == 0 )       rboptions |= RubberBandOptionTransientsMixed;
+      else if ( strcmp(params[i], "TransientsSmooth" ) == 0 )      rboptions |= RubberBandOptionTransientsSmooth;
+      else if ( strcmp(params[i], "DetectorCompound" ) == 0 )      rboptions |= RubberBandOptionDetectorCompound;
+      else if ( strcmp(params[i], "DetectorPercussive" ) == 0 )    rboptions |= RubberBandOptionDetectorPercussive;
+      else if ( strcmp(params[i], "DetectorSoft" ) == 0 )          rboptions |= RubberBandOptionDetectorSoft;
+      else if ( strcmp(params[i], "PhaseLaminar" ) == 0 )          rboptions |= RubberBandOptionPhaseLaminar;
+      else if ( strcmp(params[i], "PhaseIndependent" ) == 0 )      rboptions |= RubberBandOptionPhaseIndependent;
+      else if ( strcmp(params[i], "ThreadingAuto" ) == 0 )         rboptions |= RubberBandOptionThreadingAuto;
+      else if ( strcmp(params[i], "ThreadingNever" ) == 0 )        rboptions |= RubberBandOptionThreadingNever;
+      else if ( strcmp(params[i], "ThreadingAlways" ) == 0 )       rboptions |= RubberBandOptionThreadingAlways;
+      else if ( strcmp(params[i], "WindowStandard" ) == 0 )        rboptions |= RubberBandOptionWindowStandard;
+      else if ( strcmp(params[i], "WindowShort" ) == 0 )           rboptions |= RubberBandOptionWindowShort;
+      else if ( strcmp(params[i], "WindowLong" ) == 0 )            rboptions |= RubberBandOptionWindowLong;
+      else if ( strcmp(params[i], "SmoothingOff" ) == 0 )          rboptions |= RubberBandOptionSmoothingOff;
+      else if ( strcmp(params[i], "SmoothingOn" ) == 0 )           rboptions |= RubberBandOptionSmoothingOn;
+      else if ( strcmp(params[i], "FormantShifted" ) == 0 )        rboptions |= RubberBandOptionFormantShifted;
+      else if ( strcmp(params[i], "FormantPreserved" ) == 0 )      rboptions |= RubberBandOptionFormantPreserved;
+      else if ( strcmp(params[i], "PitchHighSpeed" ) == 0 )        rboptions |= RubberBandOptionPitchHighSpeed;
+      else if ( strcmp(params[i], "PitchHighQuality" ) == 0 )      rboptions |= RubberBandOptionPitchHighQuality;
+      else if ( strcmp(params[i], "PitchHighConsistency" ) == 0 )  rboptions |= RubberBandOptionPitchHighConsistency;
+      else if ( strcmp(params[i], "ChannelsApart" ) == 0 )         rboptions |= RubberBandOptionChannelsApart;
+      else if ( strcmp(params[i], "ChannelsTogether" ) == 0 )      rboptions |= RubberBandOptionChannelsTogether;
+      else {
+        AUBIO_ERR("rubberband_utils: did not understand option '%s', should be one of: "
+          "StretchElastic|StretchPrecise, TransientsCrisp|TransientsMixed|TransientsSmooth, "
+          "DetectorCompound|DetectorPercussive|DetectorSoft, PhaseLaminar|PhaseIndependent, "
+          "ThreadingAuto|ThreadingNever|ThreadingAlways, WindowStandard|WindowLong|WindowShort, "
+          "SmoothingOn|SmoothingOff, FormantShifted|FormantPreserved, "
+          "PitchHighSpeed|PitchHighQuality|PitchHighConsistency, ChannelsApart|ChannelsTogether\n"
+          , params[i]);
+        rboptions = -1;
+      }
+      AUBIO_FREE(params[i]);
+      i++;
+    }
+    AUBIO_FREE(params);
+  }
+  return rboptions;
+}
+
+#endif
--- /dev/null
+++ b/src/effects/timestretch.h
@@ -1,0 +1,198 @@
+/*
+  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
+
+  pitchscale is a frequency ratio. It should be in the range [0.25, 4].
+
+  \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 */
--- /dev/null
+++ b/src/effects/timestretch_dummy.c
@@ -1,0 +1,102 @@
+/*
+  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 "aubio_priv.h"
+
+#ifndef HAVE_RUBBERBAND
+
+#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 */
--- /dev/null
+++ b/src/effects/timestretch_rubberband.c
@@ -1,0 +1,261 @@
+/*
+  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 "aubio_priv.h"
+
+#ifdef HAVE_RUBBERBAND
+
+#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 ((sint_t)hopsize <= 0) {
+    AUBIO_ERR("timestretch: hopsize should be > 0, got %d\n", hopsize);
+    goto beach;
+  }
+
+  if ((sint_t)samplerate <= 0) {
+    AUBIO_ERR("timestretch: samplerate should be > 0, got %d\n", samplerate);
+    goto beach;
+  }
+
+  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;
+}
+
+#if 0
+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);
+}
+#endif
+
+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_ERR("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_ERR("timestretch: could not set stretch ratio to '%f',"
+        " should be in the range [%.2f, %.2f].\n", stretch,
+        MIN_STRETCH_RATIO, MAX_STRETCH_RATIO);
+    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_ERR("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_ERR("timestretch: could not set pitchscale to '%f',"
+        " should be in the range [0.0625, 4.].\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_ERR("timestretch: could not set transpose to '%f',"
+        " should be in the range [-24; 24].\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
--- a/src/wscript_build
+++ b/src/wscript_build
@@ -6,6 +6,7 @@
 uselib += ['INTEL_IPP']
 uselib += ['SAMPLERATE']
 uselib += ['SNDFILE']
+uselib += ['RUBBERBAND']
 uselib += ['AVCODEC']
 uselib += ['AVFORMAT']
 uselib += ['SWRESAMPLE']
--- /dev/null
+++ b/tests/src/effects/test-pitchshift.c
@@ -1,0 +1,110 @@
+#define AUBIO_UNSTABLE 1
+#include <aubio.h>
+#include "utils_tests.h"
+
+int test_wrong_params(void);
+
+int main (int argc, char **argv)
+{
+  sint_t err = 0;
+
+  if (argc < 3) {
+    PRINT_ERR("not enough arguments, running tests\n");
+    err = test_wrong_params();
+    PRINT_MSG("usage: %s <input_path> <output_path> [transpose] ", argv[0]);
+    PRINT_MSG("[mode] [hop_size] [samplerate]\n");
+    PRINT_MSG(" with [transpose] a number of semi tones in the range "
+        " [-24, 24], and [mode] in 'default', 'crispness:0',"
+        " 'crispness:1', ... 'crispness:6'\n");
+    return err;
+  }
+
+#ifdef HAVE_RUBBERBAND
+  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 = 0.;
+
+  if ( argc >= 4 ) 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]);
+
+  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\n",
+      n_frames, samplerate, n_frames / hop_size,
+      source_path);
+  PRINT_MSG("wrote to %s with hop_size: %d, samplerate: %d, latency %d\n",
+      sink_path, hop_size, samplerate, aubio_pitchshift_get_latency(ps));
+
+  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:
+  aubio_cleanup();
+#else
+  err = 0;
+  PRINT_ERR("aubio was not compiled with rubberband\n");
+#endif
+  return err;
+}
+
+int test_wrong_params(void)
+{
+  const char_t *mode = "default";
+  smpl_t transpose = 0.;
+  uint_t hop_size = 256;
+  uint_t samplerate = 44100;
+
+  if (new_aubio_pitchshift("??", transpose, hop_size, samplerate)) return 1;
+  if (new_aubio_pitchshift(mode,       28., hop_size, samplerate)) return 1;
+  if (new_aubio_pitchshift(mode, transpose,        0, samplerate)) return 1;
+  if (new_aubio_pitchshift(mode, transpose, hop_size,          0)) return 1;
+
+  aubio_pitchshift_t *p = new_aubio_pitchshift(mode, transpose,
+      hop_size, samplerate);
+#ifdef HAVE_RUBBERBAND
+  if (!p) return 1;
+  if (!aubio_pitchshift_set_pitchscale(p, 0.1)) return 1;
+  if (!aubio_pitchshift_set_transpose(p, -30)) return 1;
+  del_aubio_pitchshift(p);
+#else
+  if (p) return 1;
+#endif
+
+  return run_on_default_source_and_sink(main);
+}
--- /dev/null
+++ b/tests/src/effects/test-timestretch.c
@@ -1,0 +1,153 @@
+#define AUBIO_UNSTABLE 1
+#include <aubio.h>
+#include "utils_tests.h"
+
+int test_wrong_params(void);
+
+int main (int argc, char **argv)
+{
+  sint_t err = 0;
+
+  if (argc < 3 || argc >= 9) {
+    PRINT_ERR("wrong number of arguments, running tests\n");
+    err = test_wrong_params();
+    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;
+  }
+
+#ifdef HAVE_RUBBERBAND
+  uint_t samplerate = 0; // using source samplerate
+  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";
+
+  if ( argc >= 4 ) 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]);
+
+  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 || !in) { 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);
+  del_fvec(in);
+beach_fvec:
+  del_aubio_source(s);
+beach_source:
+#else
+  err = 0;
+  PRINT_ERR("aubio was not compiled with rubberband\n");
+#endif
+  return err;
+}
+
+int test_wrong_params(void)
+{
+  const char_t *mode = "default";
+  smpl_t stretch = 1.;
+  uint_t hop_size = 256;
+  uint_t samplerate = 44100;
+
+  if (new_aubio_timestretch("ProcessOffline:?:", stretch, hop_size, samplerate)) return 1;
+  if (new_aubio_timestretch("", stretch, hop_size, samplerate)) return 1;
+  if (new_aubio_timestretch(mode,     41., hop_size, samplerate)) return 1;
+  if (new_aubio_timestretch(mode, stretch,        0, samplerate)) return 1;
+  if (new_aubio_timestretch(mode, stretch, hop_size,          0)) return 1;
+
+  aubio_timestretch_t *p = new_aubio_timestretch(mode, stretch, hop_size,
+      samplerate);
+#ifdef HAVE_RUBBERBAND
+  if (!p) return 1;
+
+  if (aubio_timestretch_get_latency(p) == 0) return 1;
+
+  if (aubio_timestretch_get_samplerate(p) != samplerate) return 1;
+
+  aubio_timestretch_reset(p);
+
+  if (aubio_timestretch_get_transpose(p) != 0) return 1;
+  if (aubio_timestretch_set_transpose(p, 2.)) return 1;
+  if (fabs(aubio_timestretch_get_transpose(p) - 2.) >= 1e-6) return 1;
+  if (!aubio_timestretch_set_transpose(p, 200.)) return 1;
+  if (!aubio_timestretch_set_transpose(p, -200.)) return 1;
+  if (aubio_timestretch_set_transpose(p, 0.)) return 1;
+
+  if (aubio_timestretch_get_pitchscale(p) != 1) return 1;
+  if (aubio_timestretch_set_pitchscale(p, 2.)) return 1;
+  if (fabs(aubio_timestretch_get_pitchscale(p) - 2.) >= 1e-6) return 1;
+  if (!aubio_timestretch_set_pitchscale(p, 0.)) return 1;
+  if (!aubio_timestretch_set_pitchscale(p, 6.)) return 1;
+
+  if (aubio_timestretch_get_stretch(p) != stretch) return 1;
+  if (aubio_timestretch_set_stretch(p, 2.)) return 1;
+  if (fabs(aubio_timestretch_get_stretch(p) - 2.) >= 1e-6) return 1;
+  if (!aubio_timestretch_set_stretch(p, 0.)) return 1;
+  if (!aubio_timestretch_set_stretch(p, 41.)) return 1;
+
+  del_aubio_timestretch(p);
+#else
+  if (p) return 1;
+#endif
+
+  return run_on_default_source_and_sink(main);
+}
--- a/wscript
+++ b/wscript
@@ -81,6 +81,9 @@
     add_option_enable_disable(ctx, 'samplerate', default = None,
             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)',
             help_disable_str = 'do not use memcpy hacks')
@@ -140,6 +143,7 @@
                 'sndfile',
                 'samplerate',
                 'jack',
+                'rubberband',
                 'avcodec',
                 'blas',
                 'fftw3',
@@ -415,6 +419,12 @@
         ctx.check_cfg(package = 'samplerate',
                 args = '--cflags --libs samplerate >= 0.0.15',
                 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
     if (ctx.options.enable_jack != False):