src/synth/sampler.c: refactor interface, add file opening thread and utility functions
authorPaul Brossier <piem@piem.org>
Tue, 4 Oct 2016 21:16:14 +0000 (23:16 +0200)
committerPaul Brossier <piem@piem.org>
Tue, 4 Oct 2016 21:16:14 +0000 (23:16 +0200)
src/synth/sampler.c
src/synth/sampler.h

index 7df1b5f..079087b 100644 (file)
 #include "io/source.h"
 #include "synth/sampler.h"
 
+#define HAVE_THREADS 1
+#if 0
+#undef HAVE_THREADS
+#endif
+
+#ifdef HAVE_THREADS
+#include <pthread.h>
+#endif
+
 struct _aubio_sampler_t {
   uint_t samplerate;
   uint_t blocksize;
   aubio_source_t *source;
-  fvec_t *source_output;
-  fmat_t *source_output_multi;
-  char_t *uri;
+  const char_t *uri;
   uint_t playing;
+  uint_t opened;
+  uint_t loop;
+  uint_t finished;
+  uint_t eof;
+#ifdef HAVE_THREADS
+  pthread_t open_thread;        // file opening thread
+  pthread_mutex_t open_mutex;
+  uint_t waited;                // number of frames skipped while opening
+  const char_t *next_uri;
+  uint_t open_thread_running;
+  sint_t available;             // number of samples currently available
+  uint_t started;               // source warmed up
+  uint_t finish;                // flag to tell reading thread to exit
+#endif
 };
 
-aubio_sampler_t *new_aubio_sampler(uint_t samplerate, uint_t blocksize)
+aubio_sampler_t *new_aubio_sampler(uint_t blocksize, uint_t samplerate)
 {
   aubio_sampler_t *s = AUBIO_NEW(aubio_sampler_t);
   if ((sint_t)blocksize < 1) {
@@ -45,10 +66,20 @@ aubio_sampler_t *new_aubio_sampler(uint_t samplerate, uint_t blocksize)
   }
   s->samplerate = samplerate;
   s->blocksize = blocksize;
-  s->source_output = new_fvec(blocksize);
-  s->source_output_multi = new_fmat(4, blocksize);
   s->source = NULL;
   s->playing = 0;
+  s->loop = 0;
+  s->uri = NULL;
+  s->finished = 1;
+  s->eof = 0;
+  s->opened = 0;
+
+#ifdef HAVE_THREADS
+  pthread_mutex_init(&s->open_mutex, 0);
+  s->waited = 0;
+  s->open_thread = 0;
+  s->open_thread_running = 0;
+#endif
   return s;
 beach:
   AUBIO_FREE(s);
@@ -57,53 +88,228 @@ beach:
 
 uint_t aubio_sampler_load( aubio_sampler_t * o, const char_t * uri )
 {
-  if (o->source) del_aubio_source(o->source);
+  uint_t ret = AUBIO_FAIL;
+  aubio_source_t *oldsource = o->source, *newsource = NULL;
+  newsource = new_aubio_source(uri, o->samplerate, o->blocksize);
+  if (newsource) {
+    o->source = newsource;
+    if (oldsource) del_aubio_source(oldsource);
+    if (o->samplerate == 0) {
+      o->samplerate = aubio_source_get_samplerate(o->source);
+    }
+    o->uri = uri;
+    o->finished = 0;
+    o->eof = 0;
+    o->opened = 1;
+    ret = AUBIO_OK;
+    //AUBIO_WRN("sampler: loaded %s\n", o->uri);
+  } else {
+    o->source = NULL;
+    if (oldsource) del_aubio_source(oldsource);
+    o->playing = 0;
+    o->uri = NULL;
+    o->finished = 1;
+    o->eof = 0;
+    o->opened = 0;
+    AUBIO_WRN("sampler: failed loading %s\n", uri);
+  }
+  return ret;
+}
+
+#ifdef HAVE_THREADS
+static void *aubio_sampler_openfn(void *z) {
+  aubio_sampler_t *p = z;
+  uint_t err;
+  int oldtype;
+  void *ret;
+  pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
+  pthread_mutex_lock(&p->open_mutex);
+  p->open_thread_running = 1;
+  err = aubio_sampler_load(p, p->next_uri);
+  p->open_thread_running = 0;
+  pthread_mutex_unlock(&p->open_mutex);
+  ret = &err;
+  pthread_exit(ret);
+}
+#endif
 
-  if (o->uri) AUBIO_FREE(o->uri);
-  o->uri = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX));
-  strncpy(o->uri, uri, strnlen(uri, PATH_MAX));
+uint_t
+aubio_sampler_queue(aubio_sampler_t *o, const char_t *uri)
+{
+#ifdef HAVE_THREADS
+  uint_t ret = AUBIO_OK;
+  /* open uri in open_thread */
+  if (o->open_thread_running) {
+    // cancel previous open_thread
+    if (pthread_cancel(o->open_thread)) {
+      AUBIO_WRN("sampler: cancelling open thread failed\n");
+      return AUBIO_FAIL;
+    } else {
+      AUBIO_WRN("sampler: previous open of %s cancelled while opening %s\n",
+          o->next_uri, uri);
+    }
+    o->open_thread_running = 0;
+  }
+  void *threadret;
+  if (o->open_thread && pthread_join(o->open_thread, &threadret)) {
+    AUBIO_WRN("sampler: joining thread failed\n");
+  }
+  if (pthread_mutex_trylock(&o->open_mutex)) {
+    AUBIO_WRN("sampler: failed locking in queue\n");
+    ret = AUBIO_FAIL;
+    goto lock_failed;
+  }
+  o->opened = 0; // while opening
+  o->started = 0;
+  o->available = 0;
+  o->next_uri = uri;
+  if (pthread_create(&o->open_thread, 0, aubio_sampler_openfn, o) != 0) {
+    AUBIO_ERR("sampler: failed creating opening thread\n");
+    ret = AUBIO_FAIL;
+    goto thread_create_failed;
+  }
+
+thread_create_failed:
+  pthread_mutex_unlock(&o->open_mutex);
+lock_failed:
+  if (ret == AUBIO_OK) {
+    //AUBIO_WRN("sampler: queued %s\n", uri);
+  } else {
+    AUBIO_ERR("sampler: queueing %s failed\n", uri);
+  }
+  return ret;
+#else
+  AUBIO_WRN("sampler: opening %s, not queueing (not compiled with threading)\n", uri);
+  return aubio_sampler_load(o, uri);
+#endif
+}
 
-  o->source = new_aubio_source(uri, o->samplerate, o->blocksize);
-  if (o->source) return 0;
-  AUBIO_ERR("sampler: failed loading %s", uri);
-  return 1;
+void
+aubio_sampler_fetch_from_source(aubio_sampler_t *o, fvec_t *output, uint_t *read) {
+  if (o->opened == 1 && o->source && !o->finished)
+    aubio_source_do(o->source, output, read);
 }
 
-void aubio_sampler_do ( aubio_sampler_t * o, const fvec_t * input, fvec_t * output)
+void
+aubio_sampler_fetch_from_source_multi(aubio_sampler_t *o, fmat_t *output, uint_t *read) {
+  aubio_source_do_multi(o->source, output, read);
+}
+
+#if 0
+void
+aubio_sampler_fetch_from_array(aubio_sampler_t *o, fvec_t *output, uint_t *read) {
+  // TODO
+}
+#endif
+
+uint_t
+aubio_sampler_get_samplerate (aubio_sampler_t *o)
+{
+  return o->samplerate;
+}
+
+uint_t
+aubio_sampler_get_opened (aubio_sampler_t *o)
+{
+  return o->opened; //== 1 ? AUBIO_OK : AUBIO_FAIL;
+}
+
+uint_t
+aubio_sampler_get_finished(aubio_sampler_t *o)
+{
+  return o->finished;
+}
+
+uint_t
+aubio_sampler_get_eof (aubio_sampler_t *o)
 {
-  uint_t read = 0, i;
+  return o->eof;
+}
+
+uint_t
+aubio_sampler_get_waited_opening (aubio_sampler_t *o, uint_t waited) {
+#ifdef HAVE_THREADS
   if (o->playing) {
-    aubio_source_do (o->source, o->source_output, &read);
-    for (i = 0; i < output->length; i++) {
-      output->data[i] += o->source_output->data[i];
+    if (!o->opened) {
+      o->waited += waited;
+    } else if (o->waited) {
+      //AUBIO_WRN("sampler: waited %d frames (%.2fms) while opening %s\n",
+      //    o->waited, 1000.*o->waited/(smpl_t)o->samplerate, o->uri);
+      uint_t waited = o->waited;
+      o->waited = 0;
+      return waited;
     }
-    if (read < o->blocksize) o->playing = 0;
   }
-  if (input && input != output) {
-    for (i = 0; i < output->length; i++) {
-      output->data[i] += input->data[i];
-    }
+#endif
+  return 0;
+}
+
+uint_t
+aubio_sampler_seek(aubio_sampler_t * o, uint_t pos)
+{
+  uint_t ret = AUBIO_FAIL;
+  o->finished = 0;
+  if (!o->opened) return AUBIO_OK;
+#ifdef HAVE_THREADS
+  if (pthread_mutex_trylock(&o->open_mutex)) {
+    AUBIO_WRN("sampler: failed locking in seek\n");
+    return ret;
   }
+#endif
+  if (o->source) {
+    ret = aubio_source_seek(o->source, pos);
+  }
+#ifdef HAVE_THREADS
+  pthread_mutex_unlock(&o->open_mutex);
+#endif
+  return ret;
 }
 
-void aubio_sampler_do_multi ( aubio_sampler_t * o, const fmat_t * input, fmat_t * output)
+void
+aubio_sampler_do_eof (aubio_sampler_t * o)
 {
-  uint_t read = 0, i, j;
-  if (o->playing) {
-    aubio_source_do_multi (o->source, o->source_output_multi, &read);
-    for (i = 0; i < output->height; i++) {
-      for (j = 0; j < output->length; j++) {
-        output->data[i][j] += o->source_output_multi->data[i][j];
+  o->finished = 1;
+  o->eof = 1;
+  if (!o->loop) {
+    o->playing = 0;
+  } else {
+    aubio_sampler_seek(o, 0);
+  }
+}
+
+void aubio_sampler_do ( aubio_sampler_t * o, fvec_t * output, uint_t *read)
+{
+  o->eof = 0;
+  if (o->opened == 1 && o->playing) {
+    aubio_sampler_fetch_from_source(o, output, read);
+    if (*read < o->blocksize) {
+      aubio_sampler_do_eof (o);
+      if (*read > 0) {
+        // TODO pull (hopsize - read) frames
+        //memset(...  tail , 0)
       }
     }
-    if ( read < o->blocksize ) o->playing = 0;
+  } else {
+    fvec_zeros(output);
+    *read = 0; //output->length;
   }
-  if (input && input != output) {
-    for (i = 0; i < output->height; i++) {
-      for (j = 0; j < output->length; j++) {
-        output->data[i][j] += input->data[i][j];
+}
+
+void aubio_sampler_do_multi ( aubio_sampler_t * o, fmat_t * output, uint_t *read)
+{
+  o->eof = 0;
+  if (o->playing) {
+    aubio_sampler_fetch_from_source_multi(o, output, read);
+    if (*read < o->blocksize) {
+      aubio_sampler_do_eof (o);
+      if (*read > 0) {
+        // TODO pull (hopsize - read) frames
+        //memset(...  tail , 0)
       }
     }
+  } else {
+    fmat_zeros(output);
+    *read = 0;
   }
 }
 
@@ -118,9 +324,20 @@ uint_t aubio_sampler_set_playing ( aubio_sampler_t * o, uint_t playing )
   return 0;
 }
 
+uint_t aubio_sampler_get_loop ( aubio_sampler_t * o )
+{
+  return o->loop;
+}
+
+uint_t aubio_sampler_set_loop ( aubio_sampler_t * o, uint_t loop )
+{
+  o->loop = (loop == 1) ? 1 : 0;
+  return 0;
+}
+
 uint_t aubio_sampler_play ( aubio_sampler_t * o )
 {
-  aubio_source_seek (o->source, 0);
+  //aubio_source_seek (o->source, 0);
   return aubio_sampler_set_playing (o, 1);
 }
 
@@ -129,13 +346,38 @@ uint_t aubio_sampler_stop ( aubio_sampler_t * o )
   return aubio_sampler_set_playing (o, 0);
 }
 
+uint_t aubio_sampler_loop ( aubio_sampler_t * o )
+{
+  aubio_sampler_set_loop(o, 1);
+  aubio_sampler_seek(o, 0);
+  return aubio_sampler_set_playing (o, 1);
+}
+
+uint_t aubio_sampler_trigger ( aubio_sampler_t * o )
+{
+  aubio_sampler_set_loop(o, 0);
+  aubio_sampler_seek(o, 0);
+  return aubio_sampler_set_playing (o, 1);
+}
+
 void del_aubio_sampler( aubio_sampler_t * o )
 {
+#ifdef HAVE_THREADS
+  AUBIO_WRN("sampler: cleaning up\n");
+  pthread_mutex_destroy(&o->open_mutex);
+  if (o->open_thread_running) {
+    if (pthread_cancel(o->open_thread)) {
+      AUBIO_WRN("sampler: cancelling open thread failed\n");
+    }
+  }
+  void *threadret;
+  if (pthread_join(o->open_thread, &threadret)) {
+    AUBIO_WRN("sampler: joining open thread failed\n");
+  }
+  pthread_mutex_destroy(&o->open_mutex);
+#endif
   if (o->source) {
     del_aubio_source(o->source);
   }
-  if (o->uri) AUBIO_FREE(o->uri);
-  del_fvec(o->source_output);
-  del_fmat(o->source_output_multi);
   AUBIO_FREE(o);
 }
index fb1304f..f326119 100644 (file)
 
 /** \file
 
-  Load and play sound files.
+  Load and play a sound file.
 
   This file loads a sample and gets ready to play it.
 
   The `_do` function adds the new samples to the input, and write the result as
   the output.
 
+TODO:
+  - add _preset_threaded(level)
+  - add _set_stretch
+  - add _set_pitch
+
   \example synth/test-sampler.c
 
 */
@@ -49,7 +54,7 @@ typedef struct _aubio_sampler_t aubio_sampler_t;
   \return the newly created ::aubio_sampler_t
 
 */
-aubio_sampler_t * new_aubio_sampler(uint_t samplerate, uint_t hop_size);
+aubio_sampler_t * new_aubio_sampler(uint_t hop_size, uint_t samplerate);
 
 /** load source in sampler
 
@@ -61,33 +66,35 @@ aubio_sampler_t * new_aubio_sampler(uint_t samplerate, uint_t hop_size);
 */
 uint_t aubio_sampler_load( aubio_sampler_t * o, const char_t * uri );
 
+/** queue source in sampler
+
+  \param o sampler, created by new_aubio_sampler()
+  \param uri the uri of the source to load
+
+  \return 0 if successfully queued, non-zero otherwise
+
+*/
+uint_t aubio_sampler_queue(aubio_sampler_t * o, const char_t * uri );
+
 /** process sampler function
 
   \param o sampler, created by new_aubio_sampler()
-  \param input input of the sampler, to be added to the output
   \param output output of the sampler
 
-This function adds the new samples from the playing source to the output.
-
-If `input` is not NULL and different from `output`, then the samples from `input`
-are added to the output.
+This function get new samples from the playing source into output.
 
 */
-void aubio_sampler_do ( aubio_sampler_t * o, const fvec_t * input, fvec_t * output);
+void aubio_sampler_do ( aubio_sampler_t * o, fvec_t * output, uint_t *read);
 
 /** process sampler function, multiple channels
 
   \param o sampler, created by new_aubio_sampler()
-  \param input input of the sampler, to be added to the output
   \param output output of the sampler
 
-This function adds the new samples from the playing source to the output.
-
-If `input` is not NULL and different from `output`, then the samples from `input`
-are added to the output.
+This function gets new samples from the playing source into output.
 
 */
-void aubio_sampler_do_multi ( aubio_sampler_t * o, const fmat_t * input, fmat_t * output);
+void aubio_sampler_do_multi ( aubio_sampler_t * o, fmat_t * output, uint_t *read);
 
 /** get current playing state
 
@@ -108,6 +115,18 @@ uint_t aubio_sampler_get_playing ( const aubio_sampler_t * o );
 */
 uint_t aubio_sampler_set_playing ( aubio_sampler_t * o, uint_t playing );
 
+uint_t aubio_sampler_get_loop(aubio_sampler_t * o);
+
+/** set current looping state
+
+  \param o sampler, created by new_aubio_sampler()
+  \param looping 0 for not looping, 1 for looping
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_set_loop(aubio_sampler_t * o, uint_t loop);
+
 /** play sample from start
 
   \param o sampler, created by new_aubio_sampler()
@@ -117,6 +136,24 @@ uint_t aubio_sampler_set_playing ( aubio_sampler_t * o, uint_t playing );
 */
 uint_t aubio_sampler_play ( aubio_sampler_t * o );
 
+/** play sample from start, looping it
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_loop ( aubio_sampler_t * o );
+
+/** play sample from start, once
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_trigger ( aubio_sampler_t * o );
+
 /** stop sample
 
   \param o sampler, created by new_aubio_sampler()
@@ -126,6 +163,52 @@ uint_t aubio_sampler_play ( aubio_sampler_t * o );
 */
 uint_t aubio_sampler_stop ( aubio_sampler_t * o );
 
+/** get end-of-file status
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 1 when the eof is being reached, 0 otherwise
+
+*/
+uint_t aubio_sampler_get_eof(aubio_sampler_t * o);
+
+/** get end-of-file status
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return 1 when the eof is being reached, 0 otherwise
+
+*/
+uint_t aubio_sampler_get_finished (aubio_sampler_t * o);
+
+/** get samplerate
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return samplerate of the sampler
+
+*/
+uint_t aubio_sampler_get_samplerate(aubio_sampler_t * o);
+
+/** get the number of samples that were set to zero while opening a file
+
+  \param o sampler, created by new_aubio_sampler()
+
+  \return samplerate of the sampler
+
+*/
+uint_t aubio_sampler_get_waited_opening(aubio_sampler_t * o, uint_t waited);
+
+/** seek to position
+
+  \param o sampler, created by new_aubio_sampler()
+  \param pos position to seek to, in samples
+
+  \return 0 if successful, 1 otherwise
+
+*/
+uint_t aubio_sampler_seek(aubio_sampler_t * o, uint_t pos);
+
 /** destroy ::aubio_sampler_t object
 
   \param o sampler, created by new_aubio_sampler()