shithub: aubio

Download patch

ref: 74c1fb9a63dfa46c0591816bd2f450c5f2dba751
parent: c1ba75b55fb6351ca1f2dcebf89b93aee72d2711
parent: fda33947f11900fca0c3704a654d9afa6fc52224
author: Paul Brossier <piem@piem.org>
date: Wed Dec 19 12:50:42 EST 2018

Merge branch 'master' into feature/pytest

--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -10,7 +10,7 @@
 include src/*.c src/*.h
 include src/*/*.c src/*/*.h
 include examples/*.c examples/*.h
-include tests/*.h tests/*/*.c tests/*/*/*.c
+recursive-include tests *.h *.c *.py
 include python/ext/*.h
 recursive-include python *.py
 include python/README.md
--- a/python/README.md
+++ b/python/README.md
@@ -27,7 +27,7 @@
 Demos
 -----
 
-Some examples are available in the [`python/demos`][demos_dir] folder. Each
+Some examples are available in the [`python/demos` folder][demos_dir]. Each
 script is a command line program which accepts one ore more argument.
 
 **Notes**: installing additional modules is required to run some of the demos.
--- a/python/ext/aubiomodule.c
+++ b/python/ext/aubiomodule.c
@@ -223,7 +223,7 @@
   }
 
   // compute the function
-  result = Py_BuildValue (AUBIO_NPY_SMPL_CHR, fvec_alpha_norm (&vec, alpha));
+  result = PyFloat_FromDouble(fvec_alpha_norm (&vec, alpha));
   if (result == NULL) {
     return NULL;
   }
@@ -319,7 +319,7 @@
   }
 
   // compute the function
-  result = Py_BuildValue (AUBIO_NPY_SMPL_CHR, aubio_zero_crossing_rate (&vec));
+  result = PyFloat_FromDouble(aubio_zero_crossing_rate (&vec));
   if (result == NULL) {
     return NULL;
   }
--- a/python/ext/py-cvec.c
+++ b/python/ext/py-cvec.c
@@ -136,7 +136,7 @@
     goto fail;
   }
 
-  args = Py_BuildValue ("I", self->length);
+  args = PyLong_FromLong(self->length);
   if (args == NULL) {
     goto fail;
   }
--- a/python/ext/py-musicutils.c
+++ b/python/ext/py-musicutils.c
@@ -39,7 +39,7 @@
     return NULL;
   }
 
-  level_lin = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_level_lin(&vec));
+  level_lin = PyFloat_FromDouble(aubio_level_lin(&vec));
   if (level_lin == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing level_lin");
     return NULL;
@@ -67,7 +67,7 @@
     return NULL;
   }
 
-  db_spl = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_db_spl(&vec));
+  db_spl = PyFloat_FromDouble(aubio_db_spl(&vec));
   if (db_spl == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing db_spl");
     return NULL;
@@ -96,7 +96,7 @@
     return NULL;
   }
 
-  silence_detection = Py_BuildValue("I", aubio_silence_detection(&vec, threshold));
+  silence_detection = PyLong_FromLong(aubio_silence_detection(&vec, threshold));
   if (silence_detection == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing silence_detection");
     return NULL;
@@ -125,7 +125,7 @@
     return NULL;
   }
 
-  level_detection = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_level_detection(&vec, threshold));
+  level_detection = PyFloat_FromDouble(aubio_level_detection(&vec, threshold));
   if (level_detection == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing level_detection");
     return NULL;
@@ -194,9 +194,9 @@
     return NULL;
   }
   if (htk != NULL && PyObject_IsTrue(htk) == 1)
-    return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel_htk(v));
+    return PyFloat_FromDouble(aubio_hztomel_htk(v));
   else
-    return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel(v));
+    return PyFloat_FromDouble(aubio_hztomel(v));
 }
 
 PyObject*
@@ -211,9 +211,9 @@
     return NULL;
   }
   if (htk != NULL && PyObject_IsTrue(htk) == 1)
-    return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz_htk(v));
+    return PyFloat_FromDouble(aubio_meltohz_htk(v));
   else
-    return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz(v));
+    return PyFloat_FromDouble(aubio_meltohz(v));
 }
 
 PyObject*
@@ -223,7 +223,7 @@
   if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) {
     return NULL;
   }
-  return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel_htk(v));
+  return PyFloat_FromDouble(aubio_hztomel_htk(v));
 }
 
 PyObject*
@@ -233,5 +233,5 @@
   if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) {
     return NULL;
   }
-  return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz_htk(v));
+  return PyFloat_FromDouble(aubio_meltohz_htk(v));
 }
--- a/src/aubio_priv.h
+++ b/src/aubio_priv.h
@@ -367,4 +367,11 @@
 #endif
 #endif /* __STRICT_ANSI__ */
 
+#if defined(DEBUG)
+#include <assert.h>
+#define AUBIO_ASSERT(x) assert(x)
+#else
+#define AUBIO_ASSERT(x)
+#endif /* DEBUG */
+
 #endif /* AUBIO_PRIV_H */
--- a/src/io/ioutils.c
+++ b/src/io/ioutils.c
@@ -51,3 +51,39 @@
   }
   return AUBIO_OK;
 }
+
+uint_t
+aubio_sink_validate_input_length(const char_t *kind, const char_t *path,
+    uint_t max_size, uint_t write_data_length, uint_t write)
+{
+  uint_t can_write = write;
+
+  if (write > max_size) {
+    AUBIO_WRN("%s: partial write to %s, trying to write %d frames,"
+        " at most %d can be written at once\n", kind, path, write, max_size);
+    can_write = max_size;
+  }
+
+  if (can_write > write_data_length) {
+    AUBIO_WRN("%s: partial write to %s, trying to write %d frames,"
+        " but found input of length %d\n", kind, path, write,
+        write_data_length);
+    can_write = write_data_length;
+  }
+
+  return can_write;
+}
+
+uint_t
+aubio_sink_validate_input_channels(const char_t *kind, const char_t *path,
+    uint_t sink_channels, uint_t write_data_height)
+{
+  uint_t channels = sink_channels;
+  if (write_data_height < sink_channels) {
+    AUBIO_WRN("%s: partial write to %s, trying to write %d channels,"
+        " but found input of height %d\n", kind, path, sink_channels,
+        write_data_height);
+    channels = write_data_height;
+  }
+  return channels;
+}
--- a/src/io/ioutils.h
+++ b/src/io/ioutils.h
@@ -53,6 +53,33 @@
 uint_t aubio_io_validate_channels(const char_t *kind, const char_t *path,
     uint_t channels);
 
+/** validate length of input
+
+  \param kind       the object kind to report on
+  \param path       the path to report on
+  \param max_size   maximum number of frames that can be written
+  \param write_data_length actual length of input vector/matrix
+  \param write number of samples asked
+
+  \return write or the maximum number of frames that can be written
+*/
+uint_t
+aubio_sink_validate_input_length(const char_t *kind, const char_t *path,
+    uint_t max_size, uint_t write_data_length, uint_t write);
+
+/** validate height of input
+
+  \param kind       the object kind to report on
+  \param path       the path to report on
+  \param max_size   maximum number of channels that can be written
+  \param write_data_height actual height of input matrix
+
+  \return write_data_height or the maximum number of channels
+*/
+uint_t
+aubio_sink_validate_input_channels(const char_t *kind, const char_t *path,
+    uint_t sink_channels, uint_t write_data_height);
+
 #ifdef __cplusplus
 }
 #endif
--- a/src/io/sink.c
+++ b/src/io/sink.c
@@ -102,7 +102,7 @@
   !defined(HAVE_SINK_APPLE_AUDIO)
   AUBIO_ERROR("sink: failed creating '%s' at %dHz (no sink built-in)\n", uri, samplerate);
 #endif
-  AUBIO_FREE(s);
+  del_aubio_sink(s);
   return NULL;
 }
 
@@ -135,8 +135,8 @@
 }
 
 void del_aubio_sink(aubio_sink_t * s) {
-  if (!s) return;
-  s->s_del((void *)s->sink);
+  AUBIO_ASSERT(s);
+  if (s->s_del && s->sink)
+    s->s_del((void *)s->sink);
   AUBIO_FREE(s);
-  return;
 }
--- a/src/io/sink_apple_audio.c
+++ b/src/io/sink_apple_audio.c
@@ -31,9 +31,7 @@
 // ExtAudioFileRef, AudioStreamBasicDescription, AudioBufferList, ...
 #include <AudioToolbox/AudioToolbox.h>
 
-#define FLOAT_TO_SHORT(x) (short)(x * 32768)
-
-extern int createAubioBufferList(AudioBufferList *bufferList, int channels, int segmentSize);
+extern int createAudioBufferList(AudioBufferList *bufferList, int channels, int segmentSize);
 extern void freeAudioBufferList(AudioBufferList *bufferList);
 extern CFURLRef createURLFromPath(const char * path);
 char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
@@ -61,11 +59,11 @@
   s->max_frames = MAX_SIZE;
   s->async = false;
 
-  if ( (uri == NULL) || (strlen(uri) < 1) ) {
+  if ( (uri == NULL) || (strnlen(uri, PATH_MAX) < 1) ) {
     AUBIO_ERROR("sink_apple_audio: Aborted opening null path\n");
     goto beach;
   }
-  if (s->path != NULL) AUBIO_FREE(s->path);
+
   s->path = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX) + 1);
   strncpy(s->path, uri, strnlen(uri, PATH_MAX) + 1);
 
@@ -76,6 +74,7 @@
   if ((sint_t)samplerate == 0) {
     return s;
   }
+
   // invalid samplerate given, abort
   if (aubio_io_validate_samplerate("sink_apple_audio", s->path, samplerate)) {
     goto beach;
@@ -91,7 +90,7 @@
 
   return s;
 beach:
-  AUBIO_FREE(s);
+  del_aubio_sink_apple_audio(s);
   return NULL;
 }
 
@@ -102,7 +101,7 @@
   }
   s->samplerate = samplerate;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (/* s->samplerate != 0 && */ s->channels != 0) {
     return aubio_sink_apple_audio_open(s);
   }
   return AUBIO_OK;
@@ -115,7 +114,7 @@
   }
   s->channels = channels;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (s->samplerate != 0 /* && s->channels != 0 */) {
     return aubio_sink_apple_audio_open(s);
   }
   return AUBIO_OK;
@@ -150,6 +149,18 @@
   AudioFileTypeID fileType = kAudioFileWAVEType;
   CFURLRef fileURL = createURLFromPath(s->path);
   bool overwrite = true;
+
+  // set the in-memory format
+  AudioStreamBasicDescription inputFormat;
+  memset(&inputFormat, 0, sizeof(AudioStreamBasicDescription));
+  inputFormat.mFormatID         = kAudioFormatLinearPCM;
+  inputFormat.mSampleRate       = (Float64)(s->samplerate);
+  inputFormat.mFormatFlags      = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
+  inputFormat.mChannelsPerFrame = s->channels;
+  inputFormat.mBitsPerChannel   = sizeof(smpl_t) * 8;
+  inputFormat.mFramesPerPacket  = 1;
+  inputFormat.mBytesPerFrame    = inputFormat.mBitsPerChannel * inputFormat.mChannelsPerFrame / 8;
+  inputFormat.mBytesPerPacket   = inputFormat.mFramesPerPacket * inputFormat.mBytesPerFrame;
   OSStatus err = noErr;
   err = ExtAudioFileCreateWithURL(fileURL, fileType, &clientFormat, NULL,
      overwrite ? kAudioFileFlags_EraseFile : 0, &s->audioFile);
@@ -161,7 +172,18 @@
         getPrintableOSStatusError(errorstr, err));
     goto beach;
   }
-  if (createAubioBufferList(&s->bufferList, s->channels, s->max_frames * s->channels)) {
+
+  err = ExtAudioFileSetProperty(s->audioFile,
+      kExtAudioFileProperty_ClientDataFormat,
+      sizeof(AudioStreamBasicDescription), &inputFormat);
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERR("sink_apple_audio: error when trying to set output format on %s "
+        "(%s)\n", s->path, getPrintableOSStatusError(errorstr, err));
+    goto beach;
+  }
+
+  if (createAudioBufferList(&s->bufferList, s->channels, s->max_frames * s->channels)) {
     AUBIO_ERR("sink_apple_audio: error when creating buffer list for %s, "
         "out of memory? \n", s->path);
     goto beach;
@@ -174,46 +196,42 @@
 
 void aubio_sink_apple_audio_do(aubio_sink_apple_audio_t * s, fvec_t * write_data, uint_t write) {
   UInt32 c, v;
-  short *data = (short*)s->bufferList.mBuffers[0].mData;
-  if (write > s->max_frames) {
-    AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
-    write = s->max_frames;
-  }
-  smpl_t *buf = write_data->data;
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
+  uint_t length = aubio_sink_validate_input_length("sink_apple_audio", s->path,
+      s->max_frames, write_data->length, write);
 
-  if (buf) {
-      for (c = 0; c < s->channels; c++) {
-          for (v = 0; v < write; v++) {
-              data[v * s->channels + c] =
-                  FLOAT_TO_SHORT(buf[ v * s->channels + c]);
-          }
-      }
+  for (c = 0; c < s->channels; c++) {
+    for (v = 0; v < length; v++) {
+      data[v * s->channels + c] = write_data->data[v];
+    }
   }
-  aubio_sink_apple_audio_write(s, write);
+
+  aubio_sink_apple_audio_write(s, length);
 }
 
 void aubio_sink_apple_audio_do_multi(aubio_sink_apple_audio_t * s, fmat_t * write_data, uint_t write) {
   UInt32 c, v;
-  short *data = (short*)s->bufferList.mBuffers[0].mData;
-  if (write > s->max_frames) {
-    AUBIO_WRN("sink_apple_audio: trying to write %d frames, max %d\n", write, s->max_frames);
-    write = s->max_frames;
-  }
-  smpl_t **buf = write_data->data;
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
+  uint_t channels = aubio_sink_validate_input_channels("sink_apple_audio",
+      s->path, s->channels, write_data->height);
+  uint_t length = aubio_sink_validate_input_length("sink_apple_audio", s->path,
+      s->max_frames, write_data->length, write);
 
-  if (buf) {
-      for (c = 0; c < s->channels; c++) {
-          for (v = 0; v < write; v++) {
-              data[v * s->channels + c] =
-                  FLOAT_TO_SHORT(buf[c][v]);
-          }
-      }
+  for (c = 0; c < channels; c++) {
+    for (v = 0; v < length; v++) {
+      data[v * s->channels + c] = write_data->data[c][v];
+    }
   }
-  aubio_sink_apple_audio_write(s, write);
+
+  aubio_sink_apple_audio_write(s, length);
 }
 
 void aubio_sink_apple_audio_write(aubio_sink_apple_audio_t *s, uint_t write) {
   OSStatus err = noErr;
+  // set mDataByteSize to match the number of frames to be written
+  // see https://www.mail-archive.com/coreaudio-api@lists.apple.com/msg01109.html
+  s->bufferList.mBuffers[0].mDataByteSize = write * s->channels
+    * sizeof(smpl_t);
   if (s->async) {
     err = ExtAudioFileWriteAsync(s->audioFile, write, &s->bufferList);
     if (err) {
@@ -257,11 +275,13 @@
 }
 
 void del_aubio_sink_apple_audio(aubio_sink_apple_audio_t * s) {
-  if (s->audioFile) aubio_sink_apple_audio_close (s);
-  if (s->path) AUBIO_FREE(s->path);
+  AUBIO_ASSERT(s);
+  if (s->audioFile)
+    aubio_sink_apple_audio_close (s);
+  if (s->path)
+    AUBIO_FREE(s->path);
   freeAudioBufferList(&s->bufferList);
   AUBIO_FREE(s);
-  return;
 }
 
 #endif /* HAVE_SINK_APPLE_AUDIO */
--- a/src/io/sink_sndfile.c
+++ b/src/io/sink_sndfile.c
@@ -58,10 +58,9 @@
 
   if (path == NULL) {
     AUBIO_ERR("sink_sndfile: Aborted opening null path\n");
-    return NULL;
+    goto beach;
   }
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -97,7 +96,7 @@
   }
   s->samplerate = samplerate;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (/* s->samplerate != 0 && */ s->channels != 0) {
     return aubio_sink_sndfile_open(s);
   }
   return AUBIO_OK;
@@ -110,7 +109,7 @@
   }
   s->channels = channels;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (s->samplerate != 0 /* && s->channels != 0 */) {
     return aubio_sink_sndfile_open(s);
   }
   return AUBIO_OK;
@@ -147,8 +146,7 @@
   s->scratch_size = s->max_size*s->channels;
   /* allocate data for de/interleaving reallocated when needed. */
   if (s->scratch_size >= MAX_SIZE * AUBIO_MAX_CHANNELS) {
-    abort();
-    AUBIO_ERR("sink_sndfile: %d x %d exceeds maximum aubio_sink_sndfile buffer size %d\n",
+    AUBIO_ERR("sink_sndfile: %d x %d exceeds maximum buffer size %d\n",
         s->max_size, s->channels, MAX_SIZE * AUBIO_MAX_CHANNELS);
     return AUBIO_FAIL;
   }
@@ -158,24 +156,17 @@
 }
 
 void aubio_sink_sndfile_do(aubio_sink_sndfile_t *s, fvec_t * write_data, uint_t write){
-  uint_t i, j, channels = s->channels;
-  int nsamples = 0;
-  smpl_t *pwrite;
+  uint_t i, j;
   sf_count_t written_frames;
+  uint_t channels = s->channels;
+  uint_t length = aubio_sink_validate_input_length("sink_sndfile", s->path,
+      s->max_size, write_data->length, write);
+  int nsamples = channels * length;
 
-  if (write > s->max_size) {
-    AUBIO_WRN("sink_sndfile: trying to write %d frames, but only %d can be written at a time\n",
-      write, s->max_size);
-    write = s->max_size;
-  }
-
-  nsamples = channels * write;
-
   /* interleaving data  */
   for ( i = 0; i < channels; i++) {
-    pwrite = (smpl_t *)write_data->data;
-    for (j = 0; j < write; j++) {
-      s->scratch_data[channels*j+i] = pwrite[j];
+    for (j = 0; j < length; j++) {
+      s->scratch_data[channels*j+i] = write_data->data[j];
     }
   }
 
@@ -188,24 +179,18 @@
 }
 
 void aubio_sink_sndfile_do_multi(aubio_sink_sndfile_t *s, fmat_t * write_data, uint_t write){
-  uint_t i, j, channels = s->channels;
-  int nsamples = 0;
-  smpl_t *pwrite;
+  uint_t i, j;
   sf_count_t written_frames;
+  uint_t channels = aubio_sink_validate_input_channels("sink_sndfile", s->path,
+      s->channels, write_data->height);
+  uint_t length = aubio_sink_validate_input_length("sink_sndfile", s->path,
+      s->max_size, write_data->length, write);
+  int nsamples = channels * length;
 
-  if (write > s->max_size) {
-    AUBIO_WRN("sink_sndfile: trying to write %d frames, but only %d can be written at a time\n",
-      write, s->max_size);
-    write = s->max_size;
-  }
-
-  nsamples = channels * write;
-
   /* interleaving data  */
-  for ( i = 0; i < write_data->height; i++) {
-    pwrite = (smpl_t *)write_data->data[i];
-    for (j = 0; j < write; j++) {
-      s->scratch_data[channels*j+i] = pwrite[j];
+  for ( i = 0; i < channels; i++) {
+    for (j = 0; j < length; j++) {
+      s->scratch_data[channels*j+i] = write_data->data[i][j];
     }
   }
 
@@ -230,10 +215,13 @@
 }
 
 void del_aubio_sink_sndfile(aubio_sink_sndfile_t * s){
-  if (!s) return;
-  if (s->path) AUBIO_FREE(s->path);
-  aubio_sink_sndfile_close(s);
-  AUBIO_FREE(s->scratch_data);
+  AUBIO_ASSERT(s);
+  if (s->handle)
+    aubio_sink_sndfile_close(s);
+  if (s->path)
+    AUBIO_FREE(s->path);
+  if (s->scratch_data)
+    AUBIO_FREE(s->scratch_data);
   AUBIO_FREE(s);
 }
 
--- a/src/io/sink_wavwrite.c
+++ b/src/io/sink_wavwrite.c
@@ -87,12 +87,7 @@
     AUBIO_ERR("sink_wavwrite: Aborted opening null path\n");
     goto beach;
   }
-  if ((sint_t)samplerate < 0) {
-    AUBIO_ERR("sink_wavwrite: Can not create %s with samplerate %d\n", path, samplerate);
-    goto beach;
-  }
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -135,7 +130,7 @@
   }
   s->samplerate = samplerate;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (/* s->samplerate != 0 && */ s->channels != 0) {
     return aubio_sink_wavwrite_open(s);
   }
   return AUBIO_OK;
@@ -148,7 +143,7 @@
   }
   s->channels = channels;
   // automatically open when both samplerate and channels have been set
-  if (s->samplerate != 0 && s->channels != 0) {
+  if (s->samplerate != 0 /* && s->channels != 0 */) {
     return aubio_sink_wavwrite_open(s);
   }
   return AUBIO_OK;
@@ -233,19 +228,17 @@
 
 
 void aubio_sink_wavwrite_do(aubio_sink_wavwrite_t *s, fvec_t * write_data, uint_t write){
-  uint_t i = 0, written_frames = 0;
+  uint_t c = 0, i = 0, written_frames = 0;
+  uint_t length = aubio_sink_validate_input_length("sink_wavwrite", s->path,
+      s->max_size, write_data->length, write);
 
-  if (write > s->max_size) {
-    AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, "
-        "but only %d can be written at a time\n", write, s->path, s->max_size);
-    write = s->max_size;
+  for (c = 0; c < s->channels; c++) {
+    for (i = 0; i < length; i++) {
+      s->scratch_data[i * s->channels + c] = HTOLES(FLOAT_TO_SHORT(write_data->data[i]));
+    }
   }
+  written_frames = fwrite(s->scratch_data, 2, length * s->channels, s->fid);
 
-  for (i = 0; i < write; i++) {
-    s->scratch_data[i] = HTOLES(FLOAT_TO_SHORT(write_data->data[i]));
-  }
-  written_frames = fwrite(s->scratch_data, 2, write, s->fid);
-
   if (written_frames != write) {
     AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, "
         "but only %d could be written\n", write, s->path, written_frames);
@@ -257,18 +250,17 @@
 void aubio_sink_wavwrite_do_multi(aubio_sink_wavwrite_t *s, fmat_t * write_data, uint_t write){
   uint_t c = 0, i = 0, written_frames = 0;
 
-  if (write > s->max_size) {
-    AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, "
-        "but only %d can be written at a time\n", write, s->path, s->max_size);
-    write = s->max_size;
-  }
+  uint_t channels = aubio_sink_validate_input_channels("sink_wavwrite", s->path,
+      s->channels, write_data->height);
+  uint_t length = aubio_sink_validate_input_length("sink_wavwrite", s->path,
+      s->max_size, write_data->length, write);
 
-  for (c = 0; c < s->channels; c++) {
-    for (i = 0; i < write; i++) {
+  for (c = 0; c < channels; c++) {
+    for (i = 0; i < length; i++) {
       s->scratch_data[i * s->channels + c] = HTOLES(FLOAT_TO_SHORT(write_data->data[c][i]));
     }
   }
-  written_frames = fwrite(s->scratch_data, 2, write * s->channels, s->fid);
+  written_frames = fwrite(s->scratch_data, 2, length * s->channels, s->fid);
 
   if (written_frames != write * s->channels) {
     AUBIO_WRN("sink_wavwrite: trying to write %d frames to %s, "
@@ -297,10 +289,13 @@
 }
 
 void del_aubio_sink_wavwrite(aubio_sink_wavwrite_t * s){
-  if (!s) return;
-  aubio_sink_wavwrite_close(s);
-  if (s->path) AUBIO_FREE(s->path);
-  AUBIO_FREE(s->scratch_data);
+  AUBIO_ASSERT(s);
+  if (s->fid)
+    aubio_sink_wavwrite_close(s);
+  if (s->path)
+    AUBIO_FREE(s->path);
+  if (s->scratch_data)
+    AUBIO_FREE(s->scratch_data);
   AUBIO_FREE(s);
 }
 
--- a/src/io/source.c
+++ b/src/io/source.c
@@ -121,7 +121,7 @@
   AUBIO_ERROR("source: failed creating with %s at %dHz with hop size %d"
      " (no source built-in)\n", uri, samplerate, hop_size);
 #endif
-  AUBIO_FREE(s);
+  del_aubio_source(s);
   return NULL;
 }
 
@@ -138,8 +138,9 @@
 }
 
 void del_aubio_source(aubio_source_t * s) {
-  if (!s) return;
-  s->s_del((void *)s->source);
+  AUBIO_ASSERT(s);
+  if (s->s_del && s->source)
+    s->s_del((void *)s->source);
   AUBIO_FREE(s);
 }
 
--- a/src/io/source.h
+++ b/src/io/source.h
@@ -59,7 +59,6 @@
   A simple source to read from 16-bits PCM RIFF encoded WAV files.
 
   \example io/test-source.c
-  \example io/test-source_multi.c
 
 */
 
--- a/src/io/source_apple_audio.c
+++ b/src/io/source_apple_audio.c
@@ -34,8 +34,6 @@
 #define RT_BYTE3( a )      ( ((a) >> 16) & 0xff )
 #define RT_BYTE4( a )      ( ((a) >> 24) & 0xff )
 
-#define SHORT_TO_FLOAT(x) (smpl_t)(x * 3.0517578125e-05)
-
 struct _aubio_source_apple_audio_t {
   uint_t channels;
   uint_t samplerate;          //< requested samplerate
@@ -48,7 +46,7 @@
   AudioBufferList bufferList;
 };
 
-extern int createAubioBufferList(AudioBufferList *bufferList, int channels, int max_source_samples);
+extern int createAudioBufferList(AudioBufferList *bufferList, int channels, int max_source_samples);
 extern void freeAudioBufferList(AudioBufferList *bufferList);
 extern CFURLRef createURLFromPath(const char * path);
 char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
@@ -59,7 +57,7 @@
 {
   aubio_source_apple_audio_t * s = AUBIO_NEW(aubio_source_apple_audio_t);
 
-  if (path == NULL) {
+  if (path == NULL || strnlen(path, PATH_MAX) < 1) {
     AUBIO_ERROR("source_apple_audio: Aborted opening null path\n");
     goto beach;
   }
@@ -85,7 +83,7 @@
   return s;
 
 beach:
-  AUBIO_FREE(s);
+  del_aubio_source_apple_audio(s);
   return NULL;
 }
 
@@ -94,7 +92,6 @@
   OSStatus err = noErr;
   UInt32 propSize;
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -139,17 +136,16 @@
   s->channels = fileFormat.mChannelsPerFrame;
 
   AudioStreamBasicDescription clientFormat;
-  propSize = sizeof(clientFormat);
+  propSize = sizeof(AudioStreamBasicDescription);
   memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription));
   clientFormat.mFormatID         = kAudioFormatLinearPCM;
   clientFormat.mSampleRate       = (Float64)(s->samplerate);
-  clientFormat.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+  clientFormat.mFormatFlags      = kAudioFormatFlagIsFloat;
   clientFormat.mChannelsPerFrame = s->channels;
-  clientFormat.mBitsPerChannel   = sizeof(short) * 8;
+  clientFormat.mBitsPerChannel   = sizeof(smpl_t) * 8;
   clientFormat.mFramesPerPacket  = 1;
   clientFormat.mBytesPerFrame    = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8;
   clientFormat.mBytesPerPacket   = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame;
-  clientFormat.mReserved         = 0;
 
   // set the client format description
   err = ExtAudioFileSetProperty(s->audioFile, kExtAudioFileProperty_ClientDataFormat,
@@ -187,7 +183,7 @@
 
   // allocate the AudioBufferList
   freeAudioBufferList(&s->bufferList);
-  if (createAubioBufferList(&s->bufferList, s->channels, s->block_size * s->channels)) {
+  if (createAudioBufferList(&s->bufferList, s->channels, s->block_size * s->channels)) {
     AUBIO_ERR("source_apple_audio: failed creating bufferList\n");
     goto beach;
   }
@@ -196,8 +192,9 @@
   return err;
 }
 
-void aubio_source_apple_audio_do(aubio_source_apple_audio_t *s, fvec_t * read_to, uint_t * read) {
-  UInt32 c, v, loadedPackets = s->block_size;
+static UInt32 aubio_source_apple_audio_read_frame(aubio_source_apple_audio_t *s)
+{
+  UInt32 loadedPackets = s->block_size;
   OSStatus err = ExtAudioFileRead(s->audioFile, &loadedPackets, &s->bufferList);
   if (err) {
     char_t errorstr[20];
@@ -204,52 +201,42 @@
     AUBIO_ERROR("source_apple_audio: error while reading %s "
         "with ExtAudioFileRead (%s)\n", s->path,
         getPrintableOSStatusError(errorstr, err));
-    goto beach;
   }
+  return loadedPackets;
+}
 
-  short *data = (short*)s->bufferList.mBuffers[0].mData;
+void aubio_source_apple_audio_do(aubio_source_apple_audio_t *s, fvec_t * read_to,
+    uint_t * read) {
+  uint_t c, v;
+  UInt32 loadedPackets = aubio_source_apple_audio_read_frame(s);
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
 
-  smpl_t *buf = read_to->data;
-
   for (v = 0; v < loadedPackets; v++) {
-    buf[v] = 0.;
+    read_to->data[v] = 0.;
     for (c = 0; c < s->channels; c++) {
-      buf[v] += SHORT_TO_FLOAT(data[ v * s->channels + c]);
+      read_to->data[v] += data[ v * s->channels + c];
     }
-    buf[v] /= (smpl_t)s->channels;
+    read_to->data[v] /= (smpl_t)s->channels;
   }
   // short read, fill with zeros
   if (loadedPackets < s->block_size) {
     for (v = loadedPackets; v < s->block_size; v++) {
-      buf[v] = 0.;
+      read_to->data[v] = 0.;
     }
   }
 
   *read = (uint_t)loadedPackets;
   return;
-beach:
-  *read = 0;
-  return;
 }
 
 void aubio_source_apple_audio_do_multi(aubio_source_apple_audio_t *s, fmat_t * read_to, uint_t * read) {
-  UInt32 c, v, loadedPackets = s->block_size;
-  OSStatus err = ExtAudioFileRead(s->audioFile, &loadedPackets, &s->bufferList);
-  if (err) {
-    char_t errorstr[20];
-    AUBIO_ERROR("source_apple_audio: error while reading %s "
-        "with ExtAudioFileRead (%s)\n", s->path,
-        getPrintableOSStatusError(errorstr, err));
-    goto beach;
-  }
+  uint_t c, v;
+  UInt32 loadedPackets = aubio_source_apple_audio_read_frame(s);
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
 
-  short *data = (short*)s->bufferList.mBuffers[0].mData;
-
-  smpl_t **buf = read_to->data;
-
   for (v = 0; v < loadedPackets; v++) {
     for (c = 0; c < read_to->height; c++) {
-      buf[c][v] = SHORT_TO_FLOAT(data[ v * s->channels + c]);
+      read_to->data[c][v] = data[ v * s->channels + c];
     }
   }
   // if read_data has more channels than the file
@@ -257,7 +244,7 @@
     // copy last channel to all additional channels
     for (v = 0; v < loadedPackets; v++) {
       for (c = s->channels; c < read_to->height; c++) {
-        buf[c][v] = SHORT_TO_FLOAT(data[ v * s->channels + (s->channels - 1)]);
+        read_to->data[c][v] = data[ v * s->channels + (s->channels - 1)];
       }
     }
   }
@@ -265,15 +252,13 @@
   if (loadedPackets < s->block_size) {
     for (v = loadedPackets; v < s->block_size; v++) {
       for (c = 0; c < read_to->height; c++) {
-        buf[c][v] = 0.;
+        read_to->data[c][v] = 0.;
       }
     }
   }
+
   *read = (uint_t)loadedPackets;
   return;
-beach:
-  *read = 0;
-  return;
 }
 
 uint_t aubio_source_apple_audio_close (aubio_source_apple_audio_t *s)
@@ -293,11 +278,11 @@
 }
 
 void del_aubio_source_apple_audio(aubio_source_apple_audio_t * s){
+  AUBIO_ASSERT(s);
   aubio_source_apple_audio_close (s);
   if (s->path) AUBIO_FREE(s->path);
   freeAudioBufferList(&s->bufferList);
   AUBIO_FREE(s);
-  return;
 }
 
 uint_t aubio_source_apple_audio_seek (aubio_source_apple_audio_t * s, uint_t pos) {
@@ -323,7 +308,7 @@
   }
   // after a short read, the bufferList size needs to resetted to prepare for a full read
   AudioBufferList *bufferList = &s->bufferList;
-  bufferList->mBuffers[0].mDataByteSize = s->block_size * s->channels * sizeof (short);
+  bufferList->mBuffers[0].mDataByteSize = s->block_size * s->channels * sizeof (smpl_t);
   // do the actual seek
   err = ExtAudioFileSeek(s->audioFile, resampled_pos);
   if (err) {
--- a/src/io/source_avcodec.c
+++ b/src/io/source_avcodec.c
@@ -46,6 +46,10 @@
 #define HAVE_AUBIO_LIBAVCODEC_DEPRECATED 1
 #endif
 
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,3,102)
+#define HAVE_AUBIO_LIBAVCODEC_TIMEBASE_FIX 1
+#endif
+
 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
 #warning "libavcodec < 56 is deprecated"
 #define av_frame_alloc  avcodec_alloc_frame
@@ -143,7 +147,6 @@
   s->hop_size = hop_size;
   s->channels = 1;
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -239,7 +242,12 @@
         av_get_media_type_string(AVMEDIA_TYPE_AUDIO), s->path);
     goto beach;
   }
+#if HAVE_AUBIO_LIBAVCODEC_TIMEBASE_FIX
+  // avoids 'skipped frames warning' with avecodec < 58, deprecated after
+  av_codec_set_pkt_timebase(avCodecCtx,
+      avFormatCtx->streams[selected_stream]->time_base);
 #endif
+#endif
 
   if ( ( err = avcodec_open2(avCodecCtx, codec, NULL) ) < 0) {
     char errorstr[256];
@@ -630,7 +638,7 @@
 }
 
 void del_aubio_source_avcodec(aubio_source_avcodec_t * s){
-  if (!s) return;
+  AUBIO_ASSERT(s);
   aubio_source_avcodec_close(s);
   if (s->output != NULL) {
     av_free(s->output);
--- a/src/io/source_sndfile.c
+++ b/src/io/source_sndfile.c
@@ -86,7 +86,6 @@
   s->hop_size = hop_size;
   s->channels = 1;
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -331,7 +330,7 @@
 }
 
 void del_aubio_source_sndfile(aubio_source_sndfile_t * s){
-  if (!s) return;
+  AUBIO_ASSERT(s);
   aubio_source_sndfile_close(s);
 #ifdef HAVE_SAMPLERATE
   if (s->resamplers != NULL) {
--- a/src/io/source_wavread.c
+++ b/src/io/source_wavread.c
@@ -91,7 +91,6 @@
     goto beach;
   }
 
-  if (s->path) AUBIO_FREE(s->path);
   s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
   strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
@@ -471,7 +470,7 @@
 }
 
 void del_aubio_source_wavread(aubio_source_wavread_t * s) {
-  if (!s) return;
+  AUBIO_ASSERT(s);
   aubio_source_wavread_close(s);
   if (s->short_output) AUBIO_FREE(s->short_output);
   if (s->output) del_fmat(s->output);
--- a/src/io/utils_apple_audio.c
+++ b/src/io/utils_apple_audio.c
@@ -12,11 +12,12 @@
 CFURLRef getURLFromPath(const char * path);
 char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
 
-int createAubioBufferList(AudioBufferList * bufferList, int channels, int max_source_samples) {
+int createAudioBufferList(AudioBufferList * bufferList, int channels,
+    int max_source_samples) {
   bufferList->mNumberBuffers = 1;
   bufferList->mBuffers[0].mNumberChannels = channels;
-  bufferList->mBuffers[0].mData = AUBIO_ARRAY(short, max_source_samples);
-  bufferList->mBuffers[0].mDataByteSize = max_source_samples * sizeof(short);
+  bufferList->mBuffers[0].mData = AUBIO_ARRAY(smpl_t, max_source_samples);
+  bufferList->mBuffers[0].mDataByteSize = max_source_samples * sizeof(smpl_t);
   return 0;
 }
 
--- /dev/null
+++ b/tests/src/io/base-sink_custom.h
@@ -1,0 +1,167 @@
+// this should be included *after* custom functions have been defined
+
+#ifndef aubio_sink_custom
+#define aubio_sink_custom "undefined"
+#endif /* aubio_sink_custom */
+
+#ifdef HAVE_AUBIO_SINK_CUSTOM
+int test_wrong_params(void);
+
+int base_main(int argc, char **argv)
+{
+  uint_t err = 0;
+  if (argc < 3 || argc >= 6) {
+    PRINT_ERR("wrong number of arguments, running tests\n");
+    err = test_wrong_params();
+    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n",
+        argv[0]);
+    return err;
+  }
+
+  uint_t samplerate = 0;
+  uint_t hop_size = 512;
+  uint_t n_frames = 0, read = 0;
+
+  char_t *source_path = argv[1];
+  char_t *sink_path = argv[2];
+
+  aubio_source_t *src = NULL;
+  aubio_sink_custom_t *snk = NULL;
+
+  if ( argc >= 4 ) samplerate = atoi(argv[3]);
+  if ( argc >= 5 ) hop_size = atoi(argv[4]);
+
+  fvec_t *vec = new_fvec(hop_size);
+  if (!vec) { err = 1; goto failure; }
+
+  src = new_aubio_source(source_path, samplerate, hop_size);
+  if (!src) { err = 1; goto failure; }
+  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(src);
+
+  snk = new_aubio_sink_custom(sink_path, samplerate);
+  if (!snk) { err = 1; goto failure; }
+
+  do {
+    aubio_source_do(src, vec, &read);
+    aubio_sink_custom_do(snk, vec, read);
+    n_frames += read;
+  } while ( read == hop_size );
+
+  PRINT_MSG("%d frames at %dHz (%d blocks) read from %s, wrote to %s\n",
+      n_frames, samplerate, n_frames / hop_size,
+      source_path, sink_path);
+
+  // close sink now (optional)
+  aubio_sink_custom_close(snk);
+
+failure:
+  if (snk)
+    del_aubio_sink_custom(snk);
+  if (src)
+    del_aubio_source(src);
+  if (vec)
+    del_fvec(vec);
+
+  return err;
+}
+
+int test_wrong_params(void)
+{
+  fvec_t *vec;
+  fmat_t *mat;
+  aubio_sink_custom_t *s;
+  char_t sink_path[PATH_MAX] = "tmp_aubio_XXXXXX";
+  uint_t samplerate = 44100;
+  uint_t hop_size = 256;
+  uint_t oversized_hop_size = 4097;
+  uint_t oversized_samplerate = 192000 * 8 + 1;
+  uint_t channels = 3;
+  uint_t oversized_channels = 1025;
+  // create temp file
+  int fd = create_temp_sink(sink_path);
+
+  if (!fd) return 1;
+
+  if (new_aubio_sink_custom(   0,   samplerate)) return 1;
+  if (new_aubio_sink_custom("\0",   samplerate)) return 1;
+  if (new_aubio_sink_custom(sink_path,      -1)) return 1;
+
+  s = new_aubio_sink_custom(sink_path, 0);
+
+  // check setting wrong parameters fails
+  if (!aubio_sink_custom_preset_samplerate(s, oversized_samplerate)) return 1;
+  if (!aubio_sink_custom_preset_channels(s, oversized_channels)) return 1;
+  if (!aubio_sink_custom_preset_channels(s, -1)) return 1;
+
+  // check setting valid parameters passes
+  if (aubio_sink_custom_preset_samplerate(s, samplerate)) return 1;
+  if (aubio_sink_custom_preset_channels(s, 1)) return 1;
+
+  // check writing a vector with valid length
+  vec = new_fvec(hop_size);
+  aubio_sink_custom_do(s, vec, hop_size);
+  // check writing more than in the input
+  aubio_sink_custom_do(s, vec, hop_size+1);
+  // check write 0 frames
+  aubio_sink_custom_do(s, vec, 0);
+  del_fvec(vec);
+
+  // check writing an oversized vector
+  vec = new_fvec(oversized_hop_size);
+  aubio_sink_custom_do(s, vec, oversized_hop_size);
+  del_fvec(vec);
+
+  // test delete without closing
+  del_aubio_sink_custom(s);
+
+  s = new_aubio_sink_custom(sink_path, 0);
+
+  // preset channels first
+  if (aubio_sink_custom_preset_channels(s, channels)) return 1;
+  if (aubio_sink_custom_preset_samplerate(s, samplerate)) return 1;
+
+  if (aubio_sink_custom_get_samplerate(s) != samplerate) return 1;
+  if (aubio_sink_custom_get_channels(s) != channels) return 1;
+
+  mat = new_fmat(channels, hop_size);
+  // check writing a vector with valid length
+  aubio_sink_custom_do_multi(s, mat, hop_size);
+  // check writing 0 frames
+  aubio_sink_custom_do_multi(s, mat, 0);
+  // check writing more than in the input
+  aubio_sink_custom_do_multi(s, mat, hop_size+1);
+  del_fmat(mat);
+
+  // check writing oversized input
+  mat = new_fmat(channels, oversized_hop_size);
+  aubio_sink_custom_do_multi(s, mat, oversized_hop_size);
+  del_fmat(mat);
+
+  // check writing undersized input
+  mat = new_fmat(channels - 1, hop_size);
+  aubio_sink_custom_do_multi(s, mat, hop_size);
+  del_fmat(mat);
+
+  aubio_sink_custom_close(s);
+  // test closing twice
+  aubio_sink_custom_close(s);
+
+  del_aubio_sink_custom(s);
+
+  // delete temp file
+  close_temp_sink(sink_path, fd);
+
+  return run_on_default_source_and_sink(base_main);
+}
+
+#else /* HAVE_AUBIO_SINK_CUSTOM */
+
+int base_main(int argc, char** argv)
+{
+  PRINT_ERR("aubio was not compiled with aubio_sink_"
+          aubio_sink_custom ", failed running %s with %d args\n",
+          argv[0], argc);
+  return 0;
+}
+
+#endif /* HAVE_AUBIO_SINK_CUSTOM */
--- /dev/null
+++ b/tests/src/io/base-source_custom.h
@@ -1,0 +1,125 @@
+// this should be included *after* custom functions have been defined
+
+#ifndef aubio_source_custom
+#define aubio_source_custom "undefined"
+#endif /* aubio_source_custom */
+
+#ifdef HAVE_AUBIO_SOURCE_CUSTOM
+int test_wrong_params(void);
+
+int base_main(int argc, char **argv)
+{
+  uint_t err = 0;
+  if (argc < 2) {
+    PRINT_ERR("not enough arguments, running tests\n");
+    err = test_wrong_params();
+    PRINT_MSG("read a wave file as a mono vector\n");
+    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
+    PRINT_MSG("examples:\n");
+    PRINT_MSG(" - read file.wav at original samplerate\n");
+    PRINT_MSG("       %s file.wav\n", argv[0]);
+    PRINT_MSG(" - read file.wav at 32000Hz\n");
+    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
+    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
+    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
+    return err;
+  }
+
+  uint_t samplerate = 0;
+  uint_t hop_size = 256;
+  uint_t n_frames = 0, read = 0;
+  if ( argc >= 3 ) samplerate = atoi(argv[2]);
+  if ( argc >= 4 ) hop_size = atoi(argv[3]);
+
+  char_t *source_path = argv[1];
+
+  aubio_source_custom_t * s =
+    new_aubio_source_custom(source_path, samplerate, hop_size);
+  fvec_t *vec = new_fvec(hop_size);
+  if (!s || !vec) { err = 1; goto beach; }
+
+  uint_t n_frames_expected = aubio_source_custom_get_duration(s);
+
+  samplerate = aubio_source_custom_get_samplerate(s);
+
+  do {
+    aubio_source_custom_do(s, vec, &read);
+    fvec_print (vec);
+    n_frames += read;
+  } while ( read == hop_size );
+
+  PRINT_MSG("read %d frames (expected %d) at %dHz (%d blocks) from %s\n",
+            n_frames, n_frames_expected, samplerate, n_frames / hop_size,
+            source_path);
+
+  // close the file (optional)
+  aubio_source_custom_close(s);
+
+beach:
+  if (vec)
+    del_fvec(vec);
+  if (s)
+    del_aubio_source_custom(s);
+  return err;
+}
+
+int test_wrong_params(void)
+{
+  char_t *uri = DEFINEDSTRING(AUBIO_TESTS_SOURCE);
+  uint_t samplerate = 44100;
+  uint_t hop_size = 512;
+  uint_t channels, read = 0;
+  fvec_t *vec;
+  fmat_t *mat;
+  aubio_source_custom_t *s;
+
+  if (new_aubio_source_custom(0,    samplerate, hop_size)) return 1;
+  if (new_aubio_source_custom("\0", samplerate, hop_size)) return 1;
+  if (new_aubio_source_custom(uri,          -1, hop_size)) return 1;
+  if (new_aubio_source_custom(uri,           0,        0)) return 1;
+
+  s = new_aubio_source_custom(uri, samplerate, hop_size);
+  if (!s) return 1;
+  channels = aubio_source_custom_get_channels(s);
+
+  // vector to read downmixed samples
+  vec = new_fvec(hop_size);
+  // matrix to read individual channels
+  mat = new_fmat(channels, hop_size);
+
+  if (aubio_source_custom_get_samplerate(s) != samplerate) return 1;
+
+  // read first hop_size frames
+  aubio_source_custom_do(s, vec, &read);
+  if (read != hop_size) return 1;
+
+  // seek to 0
+  if(aubio_source_custom_seek(s, 0)) return 1;
+
+  // read again as multiple channels
+  aubio_source_custom_do_multi(s, mat, &read);
+  if (read != hop_size) return 1;
+
+  // close the file (optional)
+  aubio_source_custom_close(s);
+  // test closing the file a second time
+  aubio_source_custom_close(s);
+
+  del_aubio_source_custom(s);
+  del_fmat(mat);
+  del_fvec(vec);
+
+  return run_on_default_source(base_main);
+}
+
+#else /* HAVE_AUBIO_SOURCE_CUSTOM */
+
+int base_main(int argc, char** argv)
+{
+  PRINT_ERR("aubio was not compiled with aubio_source_"
+          aubio_source_custom ", failed running %s with %d args\n",
+          argv[0], argc);
+  return 0;
+}
+
+#endif /* HAVE_AUBIO_SOURCE_CUSTOM */
--- a/tests/src/io/test-sink-multi.c
+++ /dev/null
@@ -1,72 +1,0 @@
-#include <aubio.h>
-#include "utils_tests.h"
-
-// same as test-sink.c, but uses aubio_source_do_multi to read multiple
-// channels
-
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [channels] [hop_size]\n", argv[0]);
-    return err;
-  }
-
-  uint_t samplerate = 0;
-  uint_t channels = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
-
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
-
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) channels = atoi(argv[4]);
-  if ( argc >= 6 ) hop_size = atoi(argv[5]);
-  if ( argc >= 7 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
-
-  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);
-  if (channels == 0 ) channels = aubio_source_get_channels(i);
-
-  fmat_t *mat = new_fmat(channels, hop_size);
-  if (!mat) { err = 1; goto beach_fmat; }
-
-  aubio_sink_t *o = new_aubio_sink(sink_path, 0);
-  if (!o) { err = 1; goto beach_sink; }
-  err = aubio_sink_preset_samplerate(o, samplerate);
-  if (err) { goto beach; }
-  err = aubio_sink_preset_channels(o, channels);
-  if (err) { goto beach; }
-
-  do {
-    aubio_source_do_multi(i, mat, &read);
-    aubio_sink_do_multi(o, mat, read);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames at %dHz in %d channels (%d blocks) from %s written to %s\n",
-      n_frames, samplerate, channels, n_frames / hop_size,
-      source_path, sink_path);
-  PRINT_MSG("wrote %s with %dHz in %d channels\n", sink_path,
-      aubio_sink_get_samplerate(o),
-      aubio_sink_get_channels(o) );
-
-beach:
-  del_aubio_sink(o);
-beach_sink:
-  del_fmat(mat);
-beach_fmat:
-  del_aubio_source(i);
-beach_source:
-  return err;
-}
--- a/tests/src/io/test-sink.c
+++ b/tests/src/io/test-sink.c
@@ -1,14 +1,16 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
+int test_wrong_params(void);
 
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n", argv[0]);
+int main(int argc, char **argv)
+{
+  uint_t err = 0;
+  if (argc < 3 || argc >= 6) {
+    PRINT_ERR("wrong number of arguments, running tests\n");
+    err = test_wrong_params();
+    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n",
+        argv[0]);
     return err;
   }
 
@@ -19,40 +21,131 @@
   char_t *source_path = argv[1];
   char_t *sink_path = argv[2];
 
+  aubio_source_t *src = NULL;
+  aubio_sink_t *snk = NULL;
+
   if ( argc >= 4 ) samplerate = atoi(argv[3]);
   if ( argc >= 5 ) hop_size = atoi(argv[4]);
-  if ( argc >= 6 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
 
   fvec_t *vec = new_fvec(hop_size);
-  if (!vec) { err = 1; goto beach_fvec; }
+  if (!vec) { err = 1; goto failure; }
 
-  aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size);
-  if (!i) { err = 1; goto beach_source; }
+  src = new_aubio_source(source_path, samplerate, hop_size);
+  if (!src) { err = 1; goto failure; }
+  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(src);
 
-  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i);
+  snk = new_aubio_sink(sink_path, samplerate);
+  if (!snk) { err = 1; goto failure; }
 
-  aubio_sink_t *o = new_aubio_sink(sink_path, samplerate);
-  if (!o) { err = 1; goto beach_sink; }
-
   do {
-    aubio_source_do(i, vec, &read);
-    aubio_sink_do(o, vec, read);
+    aubio_source_do(src, vec, &read);
+    aubio_sink_do(snk, vec, read);
     n_frames += read;
   } while ( read == hop_size );
 
-  PRINT_MSG("read %d frames at %dHz (%d blocks) from %s written to %s\n",
+  PRINT_MSG("%d frames read at %dHz (%d blocks) from %s and written to %s\n",
       n_frames, samplerate, n_frames / hop_size,
       source_path, sink_path);
 
-  del_aubio_sink(o);
-beach_sink:
-  del_aubio_source(i);
-beach_source:
-  del_fvec(vec);
-beach_fvec:
+  // close sink now (optional)
+  aubio_sink_close(snk);
+
+failure:
+  if (snk)
+    del_aubio_sink(snk);
+  if (src)
+    del_aubio_source(src);
+  if (vec)
+    del_fvec(vec);
+
   return err;
+}
+
+int test_wrong_params(void)
+{
+  fvec_t *vec;
+  fmat_t *mat;
+  aubio_sink_t *s;
+  char_t sink_path[PATH_MAX] = "tmp_aubio_XXXXXX";
+  uint_t samplerate = 44100;
+  uint_t hop_size = 256;
+  uint_t oversized_hop_size = 4097;
+  uint_t oversized_samplerate = 192000 * 8 + 1;
+  uint_t channels = 3;
+  uint_t oversized_channels = 1025;
+  // create temp file
+  int fd = create_temp_sink(sink_path);
+
+  if (!fd) return 1;
+
+  if (new_aubio_sink(   0,   samplerate)) return 1;
+  if (new_aubio_sink("\0",   samplerate)) return 1;
+  if (new_aubio_sink(sink_path,      -1)) return 1;
+
+  s = new_aubio_sink(sink_path, 0);
+
+  // check setting wrong parameters fails
+  if (!aubio_sink_preset_samplerate(s, oversized_samplerate)) return 1;
+  if (!aubio_sink_preset_channels(s, oversized_channels)) return 1;
+  if (!aubio_sink_preset_channels(s, -1)) return 1;
+
+  // check setting valid parameters passes
+  if (aubio_sink_preset_samplerate(s, samplerate)) return 1;
+  if (aubio_sink_preset_channels(s, 1)) return 1;
+
+  // check writing a vector with valid length
+  vec = new_fvec(hop_size);
+  aubio_sink_do(s, vec, hop_size);
+  // check writing more than in the input
+  aubio_sink_do(s, vec, hop_size+1);
+  // check write 0 frames
+  aubio_sink_do(s, vec, 0);
+  del_fvec(vec);
+
+  // check writing an oversized vector
+  vec = new_fvec(oversized_hop_size);
+  aubio_sink_do(s, vec, oversized_hop_size);
+  del_fvec(vec);
+
+  // test delete without closing
+  del_aubio_sink(s);
+
+  s = new_aubio_sink(sink_path, 0);
+
+  // preset channels first
+  if (aubio_sink_preset_channels(s, channels)) return 1;
+  if (aubio_sink_preset_samplerate(s, samplerate)) return 1;
+
+  if (aubio_sink_get_samplerate(s) != samplerate) return 1;
+  if (aubio_sink_get_channels(s) != channels) return 1;
+
+  mat = new_fmat(channels, hop_size);
+  // check writing a vector with valid length
+  aubio_sink_do_multi(s, mat, hop_size);
+  // check writing 0 frames
+  aubio_sink_do_multi(s, mat, 0);
+  // check writing more than in the input
+  aubio_sink_do_multi(s, mat, hop_size+1);
+  del_fmat(mat);
+
+  // check writing oversized input
+  mat = new_fmat(channels, oversized_hop_size);
+  aubio_sink_do_multi(s, mat, oversized_hop_size);
+  del_fmat(mat);
+
+  // check writing undersized input
+  mat = new_fmat(channels - 1, hop_size);
+  aubio_sink_do_multi(s, mat, hop_size);
+  del_fmat(mat);
+
+  aubio_sink_close(s);
+  // test closing twice
+  aubio_sink_close(s);
+
+  del_aubio_sink(s);
+
+  // delete temp file
+  close_temp_sink(sink_path, fd);
+
+  return run_on_default_source_and_sink(main);
 }
--- a/tests/src/io/test-sink_apple_audio-multi.c
+++ /dev/null
@@ -1,78 +1,0 @@
-#define AUBIO_UNSTABLE 1
-#include <aubio.h>
-#include "utils_tests.h"
-
-// this file uses the unstable aubio api to test aubio_sink_apple_audio, please
-// use aubio_sink instead see src/io/sink.h and tests/src/sink/test-sink.c
-
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [channels] [hop_size]\n", argv[0]);
-    return err;
-  }
-
-#ifdef HAVE_SINK_APPLE_AUDIO
-  uint_t samplerate = 0;
-  uint_t channels = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
-
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
-
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) channels = atoi(argv[4]);
-  if ( argc >= 6 ) hop_size = atoi(argv[5]);
-  if ( argc >= 7 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
-
-  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);
-  if (channels == 0 ) channels = aubio_source_get_channels(i);
-
-  fmat_t *mat = new_fmat(channels, hop_size);
-  if (!mat) { err = 1; goto beach_fmat; }
-
-  aubio_sink_apple_audio_t *o = new_aubio_sink_apple_audio(sink_path, 0);
-  if (!o) { err = 1; goto beach_sink; }
-  err = aubio_sink_apple_audio_preset_samplerate(o, samplerate);
-  if (err) { goto beach; }
-  err = aubio_sink_apple_audio_preset_channels(o, channels);
-  if (err) { goto beach; }
-
-  do {
-    aubio_source_do_multi(i, mat, &read);
-    aubio_sink_apple_audio_do_multi(o, mat, read);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames at %dHz in %d channels (%d blocks) from %s written to %s\n",
-      n_frames, samplerate, channels, n_frames / hop_size,
-      source_path, sink_path);
-  PRINT_MSG("wrote %s with %dHz in %d channels\n", sink_path,
-      aubio_sink_apple_audio_get_samplerate(o),
-      aubio_sink_apple_audio_get_channels(o) );
-
-beach:
-  del_aubio_sink_apple_audio(o);
-beach_sink:
-  del_fmat(mat);
-beach_fmat:
-  del_aubio_source(i);
-beach_source:
-#else /* HAVE_SINK_APPLE_AUDIO */
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_sink_apple_audio\n");
-#endif /* HAVE_SINK_APPLE_AUDIO */
-  return err;
-}
--- a/tests/src/io/test-sink_apple_audio.c
+++ b/tests/src/io/test-sink_apple_audio.c
@@ -2,66 +2,28 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_sink instead
-// see src/io/sink.h and tests/src/sink/test-sink.c
+#define aubio_sink_custom "apple_audio"
 
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_SINK_APPLE_AUDIO
-  uint_t samplerate = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
+#define HAVE_AUBIO_SINK_CUSTOM
+#define aubio_sink_custom_t aubio_sink_apple_audio_t
+#define new_aubio_sink_custom new_aubio_sink_apple_audio
+#define del_aubio_sink_custom del_aubio_sink_apple_audio
+#define aubio_sink_custom_do aubio_sink_apple_audio_do
+#define aubio_sink_custom_do_multi aubio_sink_apple_audio_do_multi
+#define aubio_sink_custom_close aubio_sink_apple_audio_close
+#define aubio_sink_custom_preset_samplerate aubio_sink_apple_audio_preset_samplerate
+#define aubio_sink_custom_preset_channels aubio_sink_apple_audio_preset_channels
+#define aubio_sink_custom_get_samplerate aubio_sink_apple_audio_get_samplerate
+#define aubio_sink_custom_get_channels aubio_sink_apple_audio_get_channels
+#endif /* HAVE_SINK_APPLE_AUDIO */
 
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
+#include "base-sink_custom.h"
 
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) hop_size = atoi(argv[4]);
-  if ( argc >= 6 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
+// this file uses the unstable aubio api, please use aubio_sink instead
+// see src/io/sink.h and tests/src/sink/test-sink.c
 
-  fvec_t *vec = 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_apple_audio_t *o = new_aubio_sink_apple_audio(sink_path, samplerate);
-  if (!o) { err = 1; goto beach_sink; }
-
-  do {
-    aubio_source_do(i, vec, &read);
-    aubio_sink_apple_audio_do(o, vec, 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_sink_apple_audio(o);
-beach_sink:
-  del_aubio_source(i);
-beach_source:
-  del_fvec(vec);
-beach_fvec:
-#else /* HAVE_SINK_APPLE_AUDIO */
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_apple_audio\n");
-#endif /* HAVE_SINK_APPLE_AUDIO */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-sink_sndfile-multi.c
+++ /dev/null
@@ -1,78 +1,0 @@
-#define AUBIO_UNSTABLE 1
-#include <aubio.h>
-#include "utils_tests.h"
-
-// this file uses the unstable aubio api to test aubio_sink_sndfile, please
-// use aubio_sink instead see src/io/sink.h and tests/src/sink/test-sink.c
-
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [channels] [hop_size]\n", argv[0]);
-    return err;
-  }
-
-#ifdef HAVE_SNDFILE
-  uint_t samplerate = 0;
-  uint_t channels = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
-
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
-
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) channels = atoi(argv[4]);
-  if ( argc >= 6 ) hop_size = atoi(argv[5]);
-  if ( argc >= 7 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
-
-  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);
-  if (channels == 0 ) channels = aubio_source_get_channels(i);
-
-  fmat_t *mat = new_fmat(channels, hop_size);
-  if (!mat) { err = 1; goto beach_fmat; }
-
-  aubio_sink_sndfile_t *o = new_aubio_sink_sndfile(sink_path, 0);
-  if (!o) { err = 1; goto beach_sink; }
-  err = aubio_sink_sndfile_preset_samplerate(o, samplerate);
-  if (err) { goto beach; }
-  err = aubio_sink_sndfile_preset_channels(o, channels);
-  if (err) { goto beach; }
-
-  do {
-    aubio_source_do_multi(i, mat, &read);
-    aubio_sink_sndfile_do_multi(o, mat, read);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames at %dHz in %d channels (%d blocks) from %s written to %s\n",
-      n_frames, samplerate, channels, n_frames / hop_size,
-      source_path, sink_path);
-  PRINT_MSG("wrote %s with %dHz in %d channels\n", sink_path,
-      aubio_sink_sndfile_get_samplerate(o),
-      aubio_sink_sndfile_get_channels(o) );
-
-beach:
-  del_aubio_sink_sndfile(o);
-beach_sink:
-  del_fmat(mat);
-beach_fmat:
-  del_aubio_source(i);
-beach_source:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_sink_sndfile\n");
-#endif /* HAVE_SNDFILE */
-  return err;
-}
--- a/tests/src/io/test-sink_sndfile.c
+++ b/tests/src/io/test-sink_sndfile.c
@@ -2,66 +2,28 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_sink instead
-// see src/io/sink.h and tests/src/sink/test-sink.c
+#define aubio_sink_custom "sndfile"
 
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_SNDFILE
-  uint_t samplerate = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
+#define HAVE_AUBIO_SINK_CUSTOM
+#define aubio_sink_custom_t aubio_sink_sndfile_t
+#define new_aubio_sink_custom new_aubio_sink_sndfile
+#define del_aubio_sink_custom del_aubio_sink_sndfile
+#define aubio_sink_custom_do aubio_sink_sndfile_do
+#define aubio_sink_custom_do_multi aubio_sink_sndfile_do_multi
+#define aubio_sink_custom_close aubio_sink_sndfile_close
+#define aubio_sink_custom_preset_samplerate aubio_sink_sndfile_preset_samplerate
+#define aubio_sink_custom_preset_channels aubio_sink_sndfile_preset_channels
+#define aubio_sink_custom_get_samplerate aubio_sink_sndfile_get_samplerate
+#define aubio_sink_custom_get_channels aubio_sink_sndfile_get_channels
+#endif /* HAVE_SNDFILE */
 
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
+#include "base-sink_custom.h"
 
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) hop_size = atoi(argv[4]);
-  if ( argc >= 6 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
+// this file uses the unstable aubio api, please use aubio_sink instead
+// see src/io/sink.h and tests/src/sink/test-sink.c
 
-  fvec_t *vec = 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_sndfile_t *o = new_aubio_sink_sndfile(sink_path, samplerate);
-  if (!o) { err = 1; goto beach_sink; }
-
-  do {
-    aubio_source_do(i, vec, &read);
-    aubio_sink_sndfile_do(o, vec, 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_sink_sndfile(o);
-beach_sink:
-  del_aubio_source(i);
-beach_source:
-  del_fvec(vec);
-beach_fvec:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_sndfile\n");
-#endif /* HAVE_SNDFILE */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-sink_wavwrite-multi.c
+++ /dev/null
@@ -1,78 +1,0 @@
-#define AUBIO_UNSTABLE 1
-#include <aubio.h>
-#include "utils_tests.h"
-
-// this file uses the unstable aubio api to test aubio_sink_wavwrite, please
-// use aubio_sink instead see src/io/sink.h and tests/src/sink/test-sink.c
-
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [channels] [hop_size]\n", argv[0]);
-    return err;
-  }
-
-#ifdef HAVE_WAVWRITE
-  uint_t samplerate = 0;
-  uint_t channels = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
-
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
-
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) channels = atoi(argv[4]);
-  if ( argc >= 6 ) hop_size = atoi(argv[5]);
-  if ( argc >= 7 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
-
-  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);
-  if (channels == 0 ) channels = aubio_source_get_channels(i);
-
-  fmat_t *mat = new_fmat(channels, hop_size);
-  if (!mat) { err = 1; goto beach_fmat; }
-
-  aubio_sink_wavwrite_t *o = new_aubio_sink_wavwrite(sink_path, 0);
-  if (!o) { err = 1; goto beach_sink; }
-  err = aubio_sink_wavwrite_preset_samplerate(o, samplerate);
-  if (err) { goto beach; }
-  err = aubio_sink_wavwrite_preset_channels(o, channels);
-  if (err) { goto beach; }
-
-  do {
-    aubio_source_do_multi(i, mat, &read);
-    aubio_sink_wavwrite_do_multi(o, mat, read);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames at %dHz in %d channels (%d blocks) from %s written to %s\n",
-      n_frames, samplerate, channels, n_frames / hop_size,
-      source_path, sink_path);
-  PRINT_MSG("wrote %s with %dHz in %d channels\n", sink_path,
-      aubio_sink_wavwrite_get_samplerate(o),
-      aubio_sink_wavwrite_get_channels(o) );
-
-beach:
-  del_aubio_sink_wavwrite(o);
-beach_sink:
-  del_fmat(mat);
-beach_fmat:
-  del_aubio_source(i);
-beach_source:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_sink_wavwrite\n");
-#endif /* HAVE_WAVWRITE */
-  return err;
-}
--- a/tests/src/io/test-sink_wavwrite.c
+++ b/tests/src/io/test-sink_wavwrite.c
@@ -2,66 +2,28 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_sink instead
-// see src/io/sink.h and tests/src/sink/test-sink.c
+#define aubio_sink_custom "wavwrite"
 
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-
-  if (argc < 3) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source_and_sink(main);
-    PRINT_MSG("usage: %s <input_path> <output_path> [samplerate] [hop_size]\n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_WAVWRITE
-  uint_t samplerate = 0;
-  uint_t hop_size = 512;
-  uint_t n_frames = 0, read = 0;
+#define HAVE_AUBIO_SINK_CUSTOM
+#define aubio_sink_custom_t aubio_sink_wavwrite_t
+#define new_aubio_sink_custom new_aubio_sink_wavwrite
+#define del_aubio_sink_custom del_aubio_sink_wavwrite
+#define aubio_sink_custom_do aubio_sink_wavwrite_do
+#define aubio_sink_custom_do_multi aubio_sink_wavwrite_do_multi
+#define aubio_sink_custom_close aubio_sink_wavwrite_close
+#define aubio_sink_custom_preset_samplerate aubio_sink_wavwrite_preset_samplerate
+#define aubio_sink_custom_preset_channels aubio_sink_wavwrite_preset_channels
+#define aubio_sink_custom_get_samplerate aubio_sink_wavwrite_get_samplerate
+#define aubio_sink_custom_get_channels aubio_sink_wavwrite_get_channels
+#endif /* HAVE_WAVWRITE */
 
-  char_t *source_path = argv[1];
-  char_t *sink_path = argv[2];
+#include "base-sink_custom.h"
 
-  if ( argc >= 4 ) samplerate = atoi(argv[3]);
-  if ( argc >= 5 ) hop_size = atoi(argv[4]);
-  if ( argc >= 6 ) {
-    err = 2;
-    PRINT_ERR("too many arguments\n");
-    return err;
-  }
+// this file uses the unstable aubio api, please use aubio_sink instead
+// see src/io/sink.h and tests/src/sink/test-sink.c
 
-  fvec_t *vec = 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_wavwrite_t *o = new_aubio_sink_wavwrite(sink_path, samplerate);
-  if (!o) { err = 1; goto beach_sink; }
-
-  do {
-    aubio_source_do(i, vec, &read);
-    aubio_sink_wavwrite_do(o, vec, 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_sink_wavwrite(o);
-beach_sink:
-  del_aubio_source(i);
-beach_source:
-  del_fvec(vec);
-beach_fvec:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_sink_wavwrite\n");
-#endif /* HAVE_WAVWRITE */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-source.c
+++ b/tests/src/io/test-source.c
@@ -1,12 +1,14 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-int main (int argc, char **argv)
+int test_wrong_params(void);
+
+int main(int argc, char **argv)
 {
   uint_t err = 0;
   if (argc < 2) {
     PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
+    err = test_wrong_params();
     PRINT_MSG("read a wave file as a mono vector\n");
     PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
     PRINT_MSG("examples:\n");
@@ -27,11 +29,10 @@
 
   char_t *source_path = argv[1];
 
-
   aubio_source_t* s =
     new_aubio_source(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
   fvec_t *vec = new_fvec(hop_size);
+  if (!s || !vec) { err = 1; goto beach; }
 
   uint_t n_frames_expected = aubio_source_get_duration(s);
 
@@ -49,11 +50,60 @@
 
   // close the file (optional)
   aubio_source_close(s);
-  // test closing the file a second time
-  aubio_source_close(s);
 
-  del_fvec (vec);
-  del_aubio_source (s);
 beach:
+  if (vec)
+    del_fvec(vec);
+  if (s)
+    del_aubio_source(s);
   return err;
+}
+
+int test_wrong_params(void)
+{
+  char_t *uri = DEFINEDSTRING(AUBIO_TESTS_SOURCE);
+  uint_t samplerate = 44100;
+  uint_t hop_size = 512;
+  uint_t channels, read = 0;
+  fvec_t *vec;
+  fmat_t *mat;
+  aubio_source_t *s;
+
+  if (new_aubio_source(0,    samplerate, hop_size)) return 1;
+  if (new_aubio_source("\0", samplerate, hop_size)) return 1;
+  if (new_aubio_source(uri,          -1, hop_size)) return 1;
+  if (new_aubio_source(uri,           0,        0)) return 1;
+
+  s = new_aubio_source(uri, samplerate, hop_size);
+  if (!s) return 1;
+  channels = aubio_source_get_channels(s);
+
+  // vector to read downmixed samples
+  vec = new_fvec(hop_size);
+  // matrix to read individual channels
+  mat = new_fmat(channels, hop_size);
+
+  if (aubio_source_get_samplerate(s) != samplerate) return 1;
+
+  // read first hop_size frames
+  aubio_source_do(s, vec, &read);
+  if (read != hop_size) return 1;
+
+  // seek to 0
+  if(aubio_source_seek(s, 0)) return 1;
+
+  // read again as multiple channels
+  aubio_source_do_multi(s, mat, &read);
+  if (read != hop_size) return 1;
+
+  // close the file (optional)
+  aubio_source_close(s);
+  // test closing the file a second time
+  aubio_source_close(s);
+
+  del_aubio_source(s);
+  del_fmat(mat);
+  del_fvec(vec);
+
+  return run_on_default_source(main);
 }
--- a/tests/src/io/test-source_apple_audio.c
+++ b/tests/src/io/test-source_apple_audio.c
@@ -2,62 +2,29 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
+#define aubio_source_custom "apple_audio"
+
+#ifdef HAVE_SOURCE_APPLE_AUDIO
+#define HAVE_AUBIO_SOURCE_CUSTOM
+#define aubio_source_custom_t aubio_source_apple_audio_t
+#define new_aubio_source_custom new_aubio_source_apple_audio
+#define del_aubio_source_custom del_aubio_source_apple_audio
+#define aubio_source_custom_get_samplerate aubio_source_apple_audio_get_samplerate
+#define aubio_source_custom_get_duration aubio_source_apple_audio_get_duration
+#define aubio_source_custom_do aubio_source_apple_audio_do
+#define aubio_source_custom_do_multi aubio_source_apple_audio_do_multi
+#define aubio_source_custom_seek aubio_source_apple_audio_seek
+#define aubio_source_custom_close aubio_source_apple_audio_close
+#define aubio_source_custom_get_channels aubio_source_apple_audio_get_channels
+#define aubio_source_custom_get_samplerate aubio_source_apple_audio_get_samplerate
+#endif /* HAVE_SOURCE_APPLE_AUDIO */
+
+#include "base-source_custom.h"
+
 // this file uses the unstable aubio api, please use aubio_source instead
 // see src/io/source.h and tests/src/source/test-source.c
 
 int main (int argc, char **argv)
 {
-  uint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.aif at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.mp3 at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.mp3 0 4096 \n", argv[0]);
-    return err;
-  }
-
-#if HAVE_SOURCE_APPLE_AUDIO
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size = atoi(argv[3]);
-
-  char_t *source_path = argv[1];
-
-
-  aubio_source_apple_audio_t * s =
-    new_aubio_source_apple_audio(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
-  fvec_t *vec = new_fvec(hop_size);
-
-  uint_t n_frames_expected = aubio_source_apple_audio_get_duration(s);
-
-  samplerate = aubio_source_apple_audio_get_samplerate(s);
-
-  do {
-    aubio_source_apple_audio_do(s, vec, &read);
-    fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames (expected %d) at %dHz (%d blocks) from %s\n",
-            n_frames, n_frames_expected, samplerate, n_frames / hop_size,
-            source_path);
-
-  del_fvec (vec);
-  del_aubio_source_apple_audio (s);
-beach:
-#else /* HAVE_SOURCE_APPLE_AUDIO */
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_apple_audio\n");
-#endif /* HAVE_SOURCE_APPLE_AUDIO */
-  return err;
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-source_avcodec.c
+++ b/tests/src/io/test-source_avcodec.c
@@ -2,62 +2,29 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_source instead
-// see src/io/source.h and tests/src/source/test-source.c
+#define aubio_source_custom "avcodec"
 
-int main (int argc, char **argv)
-{
-  uint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.wav at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_LIBAV
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size = atoi(argv[3]);
+#define HAVE_AUBIO_SOURCE_CUSTOM
+#define aubio_source_custom_t aubio_source_avcodec_t
+#define new_aubio_source_custom new_aubio_source_avcodec
+#define del_aubio_source_custom del_aubio_source_avcodec
+#define aubio_source_custom_get_samplerate aubio_source_avcodec_get_samplerate
+#define aubio_source_custom_get_duration aubio_source_avcodec_get_duration
+#define aubio_source_custom_do aubio_source_avcodec_do
+#define aubio_source_custom_do_multi aubio_source_avcodec_do_multi
+#define aubio_source_custom_seek aubio_source_avcodec_seek
+#define aubio_source_custom_close aubio_source_avcodec_close
+#define aubio_source_custom_get_channels aubio_source_avcodec_get_channels
+#define aubio_source_custom_get_samplerate aubio_source_avcodec_get_samplerate
+#endif /* HAVE_LIBAV */
 
-  char_t *source_path = argv[1];
+#include "base-source_custom.h"
 
+// this file uses the unstable aubio api, please use aubio_source instead
+// see src/io/source.h and tests/src/source/test-source.c
 
-  aubio_source_avcodec_t * s =
-    new_aubio_source_avcodec(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
-  fvec_t *vec = new_fvec(hop_size);
-
-  uint_t n_frames_expected = aubio_source_avcodec_get_duration(s);
-
-  samplerate = aubio_source_avcodec_get_samplerate(s);
-
-  do {
-    aubio_source_avcodec_do(s, vec, &read);
-    fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames (expected %d) at %dHz (%d blocks) from %s\n",
-            n_frames, n_frames_expected, samplerate, n_frames / hop_size,
-            source_path);
-
-  del_fvec (vec);
-  del_aubio_source_avcodec (s);
-beach:
-#else /* HAVE_LIBAV */
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_avcodec\n");
-#endif /* HAVE_LIBAV */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-source_multi.c
+++ /dev/null
@@ -1,57 +1,0 @@
-#include <aubio.h>
-#include "utils_tests.h"
-
-int main (int argc, char **argv)
-{
-  sint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.wav at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 256 frames blocks, mono\n");
-    PRINT_MSG("       %s file.wav 0 4096 1\n", argv[0]);
-    return err;
-  }
-
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  uint_t n_channels = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size   = atoi(argv[3]);
-  if ( argc >= 5 ) n_channels = atoi(argv[4]);
-
-  char_t *source_path = argv[1];
-
-  aubio_source_t* s = new_aubio_source(source_path, samplerate, hop_size);
-  if (!s) { err = -1; goto beach; }
-
-  if ( samplerate == 0 ) samplerate = aubio_source_get_samplerate(s);
-
-  if ( n_channels == 0 ) n_channels = aubio_source_get_channels(s);
-
-  fmat_t *mat = new_fmat(n_channels, hop_size);
-
-  do {
-    aubio_source_do_multi (s, mat, &read);
-    fmat_print (mat);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames in %d channels at %dHz (%d blocks) from %s\n",
-      n_frames, n_channels, samplerate, n_frames / hop_size, source_path);
-
-  del_fmat (mat);
-  del_aubio_source (s);
-beach:
-
-  return err;
-}
--- a/tests/src/io/test-source_seek.c
+++ /dev/null
@@ -1,92 +1,0 @@
-#include <aubio.h>
-#include "utils_tests.h"
-
-int main (int argc, char **argv)
-{
-  uint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.wav at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
-    return err;
-  }
-
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  uint_t old_n_frames_1 = 0, old_n_frames_2 = 0, old_n_frames_3 = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size = atoi(argv[3]);
-
-  char_t *source_path = argv[1];
-
-  fvec_t *vec = new_fvec(hop_size);
-
-  aubio_source_t* s = new_aubio_source(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
-
-  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(s);
-
-  do {
-    aubio_source_do(s, vec, &read);
-    //fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %.2fs, %d frames at %dHz (%d blocks) from %s\n",
-      n_frames * 1. / samplerate,
-      n_frames, samplerate,
-      n_frames / hop_size, source_path);
-
-  old_n_frames_1 = n_frames;
-
-  aubio_source_seek (s, 0);
-
-  n_frames = 0;
-  do {
-    aubio_source_do(s, vec, &read);
-    //fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %.2fs, %d frames at %dHz (%d blocks) from %s\n",
-      n_frames * 1. / samplerate,
-      n_frames, samplerate,
-      n_frames / hop_size, source_path);
-
-  old_n_frames_2 = n_frames;
-
-  aubio_source_seek (s, old_n_frames_1 / 2);
-
-  n_frames = 0;
-  do {
-    aubio_source_do(s, vec, &read);
-    //fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %.2fs, %d frames at %dHz (%d blocks) from %s\n",
-      n_frames * 1. / samplerate,
-      n_frames, samplerate,
-      n_frames / hop_size, source_path);
-
-  old_n_frames_3 = n_frames;
-
-  del_aubio_source (s);
-beach:
-  del_fvec (vec);
-
-  // check that we got exactly the same number of frames
-  assert ( old_n_frames_2 == old_n_frames_1 );
-  // check that we got about half the frames, with 3 decimals
-  assert ( roundf(1.e3 * old_n_frames_1 / old_n_frames_3) / 1.e3 == 2.);
-  return err;
-}
--- a/tests/src/io/test-source_sndfile.c
+++ b/tests/src/io/test-source_sndfile.c
@@ -2,62 +2,29 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_source instead
-// see src/io/source.h and tests/src/source/test-source.c
+#define aubio_source_custom "sndfile"
 
-int main (int argc, char **argv)
-{
-  uint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.wav at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_SNDFILE
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size = atoi(argv[3]);
+#define HAVE_AUBIO_SOURCE_CUSTOM
+#define aubio_source_custom_t aubio_source_sndfile_t
+#define new_aubio_source_custom new_aubio_source_sndfile
+#define del_aubio_source_custom del_aubio_source_sndfile
+#define aubio_source_custom_get_samplerate aubio_source_sndfile_get_samplerate
+#define aubio_source_custom_get_duration aubio_source_sndfile_get_duration
+#define aubio_source_custom_do aubio_source_sndfile_do
+#define aubio_source_custom_do_multi aubio_source_sndfile_do_multi
+#define aubio_source_custom_seek aubio_source_sndfile_seek
+#define aubio_source_custom_close aubio_source_sndfile_close
+#define aubio_source_custom_get_channels aubio_source_sndfile_get_channels
+#define aubio_source_custom_get_samplerate aubio_source_sndfile_get_samplerate
+#endif /* HAVE_LIBAV */
 
-  char_t *source_path = argv[1];
+#include "base-source_custom.h"
 
+// this file uses the unstable aubio api, please use aubio_source instead
+// see src/io/source.h and tests/src/source/test-source.c
 
-  aubio_source_sndfile_t * s =
-    new_aubio_source_sndfile(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
-  fvec_t *vec = new_fvec(hop_size);
-
-  uint_t n_frames_expected = aubio_source_sndfile_get_duration(s);
-
-  samplerate = aubio_source_sndfile_get_samplerate(s);
-
-  do {
-    aubio_source_sndfile_do(s, vec, &read);
-    fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames (expected %d) at %dHz (%d blocks) from %s\n",
-            n_frames, n_frames_expected, samplerate, n_frames / hop_size,
-            source_path);
-
-  del_fvec (vec);
-  del_aubio_source_sndfile (s);
-beach:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_sndfile\n");
-#endif /* HAVE_SNDFILE */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/io/test-source_wavread.c
+++ b/tests/src/io/test-source_wavread.c
@@ -2,62 +2,29 @@
 #include <aubio.h>
 #include "utils_tests.h"
 
-// this file uses the unstable aubio api, please use aubio_source instead
-// see src/io/source.h and tests/src/source/test-source.c
+#define aubio_source_custom "wavread"
 
-int main (int argc, char **argv)
-{
-  uint_t err = 0;
-  if (argc < 2) {
-    PRINT_ERR("not enough arguments, running tests\n");
-    err = run_on_default_source(main);
-    PRINT_MSG("read a wave file as a mono vector\n");
-    PRINT_MSG("usage: %s <source_path> [samplerate] [hop_size]\n", argv[0]);
-    PRINT_MSG("examples:\n");
-    PRINT_MSG(" - read file.wav at original samplerate\n");
-    PRINT_MSG("       %s file.wav\n", argv[0]);
-    PRINT_MSG(" - read file.wav at 32000Hz\n");
-    PRINT_MSG("       %s file.aif 32000\n", argv[0]);
-    PRINT_MSG(" - read file.wav at original samplerate with 4096 blocks\n");
-    PRINT_MSG("       %s file.wav 0 4096 \n", argv[0]);
-    return err;
-  }
-
 #ifdef HAVE_WAVREAD
-  uint_t samplerate = 0;
-  uint_t hop_size = 256;
-  uint_t n_frames = 0, read = 0;
-  if ( argc >= 3 ) samplerate = atoi(argv[2]);
-  if ( argc >= 4 ) hop_size = atoi(argv[3]);
+#define HAVE_AUBIO_SOURCE_CUSTOM
+#define aubio_source_custom_t aubio_source_wavread_t
+#define new_aubio_source_custom new_aubio_source_wavread
+#define del_aubio_source_custom del_aubio_source_wavread
+#define aubio_source_custom_get_samplerate aubio_source_wavread_get_samplerate
+#define aubio_source_custom_get_duration aubio_source_wavread_get_duration
+#define aubio_source_custom_do aubio_source_wavread_do
+#define aubio_source_custom_do_multi aubio_source_wavread_do_multi
+#define aubio_source_custom_seek aubio_source_wavread_seek
+#define aubio_source_custom_close aubio_source_wavread_close
+#define aubio_source_custom_get_channels aubio_source_wavread_get_channels
+#define aubio_source_custom_get_samplerate aubio_source_wavread_get_samplerate
+#endif /* HAVE_WAVREAD */
 
-  char_t *source_path = argv[1];
+#include "base-source_custom.h"
 
+// this file uses the unstable aubio api, please use aubio_source instead
+// see src/io/source.h and tests/src/source/test-source.c
 
-  aubio_source_wavread_t * s =
-    new_aubio_source_wavread(source_path, samplerate, hop_size);
-  if (!s) { err = 1; goto beach; }
-  fvec_t *vec = new_fvec(hop_size);
-
-  uint_t n_frames_expected = aubio_source_wavread_get_duration(s);
-
-  samplerate = aubio_source_wavread_get_samplerate(s);
-
-  do {
-    aubio_source_wavread_do(s, vec, &read);
-    fvec_print (vec);
-    n_frames += read;
-  } while ( read == hop_size );
-
-  PRINT_MSG("read %d frames (expected %d) at %dHz (%d blocks) from %s\n",
-            n_frames, n_frames_expected, samplerate, n_frames / hop_size,
-            source_path);
-
-  del_fvec (vec);
-  del_aubio_source_wavread (s);
-beach:
-#else
-  err = 0;
-  PRINT_ERR("aubio was not compiled with aubio_source_wavread\n");
-#endif /* HAVE_WAVREAD */
-  return err;
+int main (int argc, char **argv)
+{
+  return base_main(argc, argv);
 }
--- a/tests/src/onset/test-onset.c
+++ b/tests/src/onset/test-onset.c
@@ -69,21 +69,19 @@
   uint_t hop_size = win_size / 2;
   uint_t samplerate = 44100;
   // hop_size < 1
-  if (new_aubio_onset("default", 5, 0, samplerate))
-    return 1;
+  if (new_aubio_onset("default", 5, 0, samplerate)) return 1;
+
   // buf_size < 2
-  if (new_aubio_onset("default", 1, 1, samplerate))
-    return 1;
+  if (new_aubio_onset("default", 1, 1, samplerate)) return 1;
+
   // buf_size < hop_size
-  if (new_aubio_onset("default", hop_size, win_size, samplerate))
-    return 1;
+  if (new_aubio_onset("default", hop_size, win_size, samplerate)) return 1;
+
   // samplerate < 1
-  if (new_aubio_onset("default", 1024, 512, 0))
-    return 1;
+  if (new_aubio_onset("default", 1024, 512, 0)) return 1;
 
   // specdesc creation failed
-  if (new_aubio_onset("abcd", win_size, win_size/2, samplerate))
-    return 1;
+  if (new_aubio_onset("abcd", win_size, win_size/2, samplerate)) return 1;
 
   aubio_onset_t *o;
 
@@ -92,8 +90,7 @@
   if (o) del_aubio_onset(o);
 
   o = new_aubio_onset("default", win_size, hop_size, samplerate);
-  if (!aubio_onset_set_default_parameters(o, "wrong_type"))
-    return 1;
+  if (!aubio_onset_set_default_parameters(o, "wrong_type")) return 1;
   del_aubio_onset(o);
 
   return run_on_default_source(main);
--- a/tests/src/tempo/test-tempo.c
+++ b/tests/src/tempo/test-tempo.c
@@ -81,35 +81,28 @@
   uint_t i;
 
   // test wrong method fails
-  if (new_aubio_tempo("unexisting_method", win_size, hop_size, samplerate))
-    return 1;
+  if (new_aubio_tempo("undefined", win_size, hop_size, samplerate)) return 1;
 
   // test hop > win fails
-  if (new_aubio_tempo("default", hop_size, win_size, samplerate))
-    return 1;
+  if (new_aubio_tempo("default", hop_size, win_size, samplerate)) return 1;
 
   // test null hop_size fails
-  if (new_aubio_tempo("default", win_size, 0, samplerate))
-    return 1;
+  if (new_aubio_tempo("default", win_size, 0, samplerate)) return 1;
 
   // test 1 buf_size fails
-  if (new_aubio_tempo("default", 1, 1, samplerate))
-    return 1;
+  if (new_aubio_tempo("default", 1, 1, samplerate)) return 1;
 
   // test null samplerate fails
-  if (new_aubio_tempo("default", win_size, hop_size, 0))
-    return 1;
+  if (new_aubio_tempo("default", win_size, hop_size, 0)) return 1;
 
   // test short sizes workaround
   t = new_aubio_tempo("default", 2048, 2048, 500);
-  if (!t)
-    return 1;
+  if (!t) return 1;
 
   del_aubio_tempo(t);
 
   t = new_aubio_tempo("default", win_size, hop_size, samplerate);
-  if (!t)
-    return 1;
+  if (!t) return 1;
 
   in = new_fvec(hop_size);
   out = new_fvec(1);
--- a/tests/src/temporal/test-filter.c
+++ b/tests/src/temporal/test-filter.c
@@ -9,17 +9,13 @@
 
   aubio_filter_t *o = new_aubio_filter_c_weighting (44100);
 
-  if (new_aubio_filter(0))
-    return 1;
+  if (new_aubio_filter(0)) return 1;
 
-  if (aubio_filter_get_samplerate(o) != 44100)
-    return 1;
+  if (aubio_filter_get_samplerate(o) != 44100) return 1;
 
-  if (aubio_filter_set_c_weighting (o, -1) == 0)
-    return 1;
+  if (aubio_filter_set_c_weighting (o, -1) == 0) return 1;
 
-  if (aubio_filter_set_c_weighting (0, 32000) == 0)
-    return 1;
+  if (aubio_filter_set_c_weighting (0, 32000) == 0) return 1;
 
   in->data[impulse_at] = 0.5;
   fvec_print (in);
@@ -29,10 +25,9 @@
 
   o = new_aubio_filter_a_weighting (32000);
 
-  if (aubio_filter_set_a_weighting (o, -1) == 0)
-    return 1;
-  if (aubio_filter_set_a_weighting (0, 32000) == 0)
-    return 1;
+  if (aubio_filter_set_a_weighting (o, -1) == 0) return 1;
+
+  if (aubio_filter_set_a_weighting (0, 32000) == 0) return 1;
 
   in->data[impulse_at] = 0.5;
   fvec_print (in);
--- a/tests/src/test-delnull.c
+++ /dev/null
@@ -1,24 +1,0 @@
-#include <stdlib.h>
-#include "aubio.h"
-
-// When creating an aubio object, the user should check whether the object is
-// set NULL, indicating the creation failed and the object was not allocated.
-
-int main (void)
-{
-  uint_t return_code = 0;
-  fvec_t *f = new_fvec(-12);
-  cvec_t *c = new_cvec(-12);
-  lvec_t *l = new_lvec(-12);
-  aubio_fft_t *fft = new_aubio_fft(-12);
-  if (f != NULL) {
-    return_code = 1;
-  } else if (c != NULL) {
-    return_code = 2;
-  } else if (l != NULL) {
-    return_code = 3;
-  } else if (fft != NULL) {
-    return_code = 3;
-  }
-  return return_code;
-}
--- a/tests/utils_tests.h
+++ b/tests/utils_tests.h
@@ -55,21 +55,20 @@
 #define RAND_MAX 32767
 #endif
 
-// are we on windows ? or are we using -std=c99 ?
-#if defined(HAVE_WIN_HACKS) || defined(__STRICT_ANSI__)
-// http://en.wikipedia.org/wiki/Linear_congruential_generator
-// no srandom/random on win32
+#if defined(HAVE_WIN_HACKS)
 
-uint_t srandom_seed = 1029;
+// use srand/rand on windows
+#define srandom srand
+#define random rand
 
-void srandom(uint_t new_seed) {
-    srandom_seed = new_seed;
-}
+#elif defined(__STRICT_ANSI__)
 
-uint_t random(void) {
-    srandom_seed = 1664525 * srandom_seed + 1013904223;
-    return srandom_seed;
-}
+// workaround to build with -std=c99 (for instance with older cygwin),
+// assuming libbc is recent enough to supports these functions.
+extern void srandom(unsigned);
+extern int random(void);
+extern char mkstemp(const char *pat);
+
 #endif
 
 void utils_init_random (void);
--- a/wscript
+++ b/wscript
@@ -555,9 +555,10 @@
                     + bld.path.find_dir('src').ant_glob('**/*.h'),
                 target = bld.path.find_or_declare('api/index.html'),
                 cwd = bld.path.find_dir('doc'))
+        # evaluate nodes lazily to prevent build directory traversal warnings
         bld.install_files('${DATAROOTDIR}/doc/libaubio-doc/api',
-                bld.path.find_or_declare('api').ant_glob('**/*'),
-                cwd=bld.path.find_or_declare('api'),
+                bld.path.find_or_declare('api').ant_glob('**/*',
+                    generator=True), cwd=bld.path.find_or_declare('api'),
                 relative_trick=True)
 
 def sphinx(bld):
@@ -579,9 +580,10 @@
                 cwd = bld.path.find_dir('doc'),
                 source = bld.path.find_dir('doc').ant_glob('*.rst'),
                 target = bld.path.find_or_declare('manual/index.html'))
+        # evaluate nodes lazily to prevent build directory traversal warnings
         bld.install_files('${DATAROOTDIR}/doc/libaubio-doc/manual',
-                bld.path.find_or_declare('manual').ant_glob('**/*'),
-                cwd=bld.path.find_or_declare('manual'),
+                bld.path.find_or_declare('manual').ant_glob('**/*',
+                    generator=True), cwd=bld.path.find_or_declare('manual'),
                 relative_trick=True)
 
 # register the previous rules as build rules