shithub: aubio

Download patch

ref: 40bdc14d29bf86be7174fa7d36f75217cf44d423
parent: 0850e54138b7b33eb5630b92d7758d8a55f4e72b
parent: 0512fca3c22ee4257140a5eba0c993233c05febe
author: Paul Brossier <piem@piem.org>
date: Sun Dec 16 21:34:15 EST 2018

Merge branch 'master' into feature/sink_vorbis

--- 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
@@ -61,11 +61,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);
 
@@ -91,7 +91,7 @@
 
   return s;
 beach:
-  AUBIO_FREE(s);
+  del_aubio_sink_apple_audio(s);
   return NULL;
 }
 
@@ -102,7 +102,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 +115,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;
@@ -175,41 +175,33 @@
 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;
+  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] = FLOAT_TO_SHORT(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;
+  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] = FLOAT_TO_SHORT(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) {
@@ -257,11 +249,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
@@ -59,7 +59,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 +85,7 @@
   return s;
 
 beach:
-  AUBIO_FREE(s);
+  del_aubio_source_apple_audio(s);
   return NULL;
 }
 
@@ -94,7 +94,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);
 
@@ -293,11 +292,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) {
--- 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);
--- /dev/null
+++ b/tests/src/io/base-sink_custom.h
@@ -1,0 +1,158 @@
+// 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];
+
+  if ( argc >= 4 ) samplerate = atoi(argv[3]);
+  if ( argc >= 5 ) hop_size = atoi(argv[4]);
+
+  fvec_t *vec = new_fvec(hop_size);
+
+  aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size);
+  if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i);
+
+  aubio_sink_custom_t *o = new_aubio_sink_custom(sink_path, samplerate);
+
+  if (!vec || !i || !o) { err = 1; goto failure; }
+
+  do {
+    aubio_source_do(i, vec, &read);
+    aubio_sink_custom_do(o, 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(o);
+
+failure:
+  if (o)
+    del_aubio_sink_custom(o);
+  if (i)
+    del_aubio_source(i);
+  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;
+
+  mat = new_fmat(channels, hop_size);
+  // check writing a vector with valid length
+  aubio_sink_custom_do_multi(s, mat, hop_size);
+  // 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;
   }
 
@@ -21,23 +23,16 @@
 
   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; }
 
   aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size);
-  if (!i) { err = 1; goto beach_source; }
-
   if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i);
 
   aubio_sink_t *o = new_aubio_sink(sink_path, samplerate);
-  if (!o) { err = 1; goto beach_sink; }
 
+  if (!vec || !i || !o) { err = 1; goto failure; }
+
   do {
     aubio_source_do(i, vec, &read);
     aubio_sink_do(o, vec, read);
@@ -44,15 +39,104 @@
     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(o);
+
+failure:
+  if (o)
+    del_aubio_sink(o);
+  if (i)
+    del_aubio_source(i);
+  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;
+
+  mat = new_fmat(channels, hop_size);
+  // check writing a vector with valid length
+  aubio_sink_do_multi(s, mat, hop_size);
+  // 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,26 @@
 #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
+#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,26 @@
 #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
+#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,26 @@
 #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
+#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/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
@@ -565,9 +565,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):
@@ -589,9 +590,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