Index: python/demos/demo_pitchshift.py
===================================================================
--- python/demos/demo_pitchshift.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
+++ python/demos/demo_pitchshift.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
@@ -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 95ff046c698156f21e2ca0d1d8a02c23ab76969f)
+++ python/lib/gen_code.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
@@ -21,4 +21,5 @@
     'method': '"default"',
     'uri': '"none"',
+    'transpose': '0.',
     }
 
@@ -84,4 +85,5 @@
         'filterbank': 'self->n_filters',
         'tss': 'self->buf_size',
+        'pitchshift': 'self->hop_size',
         'dct': 'self->size',
         }
@@ -97,4 +99,5 @@
         'wavetable': 'self->hop_size',
         'tss': 'self->buf_size / 2 + 1',
+        'pitchshift': 'self->hop_size',
         }
 
@@ -291,4 +294,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'])
@@ -311,4 +316,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 95ff046c698156f21e2ca0d1d8a02c23ab76969f)
+++ python/lib/gen_external.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
@@ -47,4 +47,5 @@
     'audio_unit',
     'spectral_whitening',
+    'timestretch', # TODO fix parsing of uint_t *read in _do
 ]
 
Index: python/lib/moresetuptools.py
===================================================================
--- python/lib/moresetuptools.py	(revision 95ff046c698156f21e2ca0d1d8a02c23ab76969f)
+++ python/lib/moresetuptools.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
@@ -79,5 +79,7 @@
     packages = ['libavcodec', 'libavformat', 'libavutil',
                 'libswresample', 'libavresample',
+                'jack',
                 'sndfile',
+                'rubberband',
                 #'fftw3f',
                ]
@@ -101,4 +103,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 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
+++ python/tests/test_pitchshift.py	(revision 67c06d8d82bb776a5c804438799d23cdc2f3b10c)
@@ -0,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()
