source: src/effects/timestretch_rubberband.c @ 4559863

feature/timestretch
Last change on this file since 4559863 was 4559863, checked in by Paul Brossier <piem@piem.org>, 11 months ago

src/effects/timestretch_rubberband.c: improve threading

  • Property mode set to 100644
File size: 16.1 KB
Line 
1/*
2  Copyright (C) 2016 Paul Brossier <piem@aubio.org>
3
4  This file is part of aubio.
5
6  aubio is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10
11  aubio is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15
16  You should have received a copy of the GNU General Public License
17  along with aubio.  If not, see <http://www.gnu.org/licenses/>.
18
19*/
20
21#include "config.h"
22
23#ifdef HAVE_RUBBERBAND
24
25#include "aubio_priv.h"
26#include "fvec.h"
27#include "fmat.h"
28#include "io/source.h"
29#include "effects/timestretch.h"
30
31#include "rubberband/rubberband-c.h"
32
33#define MIN_STRETCH_RATIO 0.025
34#define MAX_STRETCH_RATIO 40.
35
36#define HAVE_THREADS 1
37#if 0
38#undef HAVE_THREADS
39#endif
40
41#ifdef HAVE_THREADS
42#include <pthread.h>
43#endif
44
45/** generic time stretching structure */
46struct _aubio_timestretch_t
47{
48  uint_t samplerate;              /**< samplerate */
49  uint_t hopsize;                 /**< hop size */
50  smpl_t stretchratio;            /**< time ratio */
51  smpl_t pitchscale;              /**< pitch scale */
52
53  aubio_source_t *source;
54  uint_t source_hopsize;          /**< hop size at which the source is read */
55  fvec_t *in;
56  uint_t eof;
57
58  RubberBandState rb;
59  RubberBandOptions rboptions;
60
61  uint_t opened;
62  const char_t *uri;
63#ifdef HAVE_THREADS
64  pthread_t read_thread;
65  pthread_mutex_t read_mutex;
66  pthread_cond_t read_avail;
67  pthread_cond_t read_request;
68  pthread_t open_thread;
69  pthread_mutex_t open_mutex;
70  uint_t open_thread_running;
71  sint_t available;
72  uint_t started;
73  uint_t finish;
74#endif
75};
76
77extern RubberBandOptions aubio_get_rubberband_opts(const char_t *mode);
78
79static void aubio_timestretch_warmup (aubio_timestretch_t * p);
80static sint_t aubio_timestretch_fetch(aubio_timestretch_t *p, uint_t fetch);
81#ifdef HAVE_THREADS
82static void *aubio_timestretch_readfn(void *p);
83static void *aubio_timestretch_openfn(void *z);
84#endif
85
86aubio_timestretch_t *
87new_aubio_timestretch (const char_t * uri, const char_t * mode,
88    smpl_t stretchratio, uint_t hopsize, uint_t samplerate)
89{
90  aubio_timestretch_t *p = AUBIO_NEW (aubio_timestretch_t);
91  p->hopsize = hopsize;
92  //p->source_hopsize = 2048;
93  p->source_hopsize = hopsize;
94  p->pitchscale = 1.;
95
96  if (stretchratio <= MAX_STRETCH_RATIO && stretchratio >= MIN_STRETCH_RATIO) {
97    p->stretchratio = stretchratio;
98  } else {
99    AUBIO_ERR("timestretch: stretchratio should be in the range [%.3f, %.3f], got %f\n",
100        MIN_STRETCH_RATIO, MAX_STRETCH_RATIO, stretchratio);
101    goto beach;
102  }
103
104  p->rboptions = aubio_get_rubberband_opts(mode);
105  if (p->rboptions < 0) {
106    AUBIO_ERR("timestretch: unknown time stretching method %s\n", mode);
107    goto beach;
108  }
109
110  p->in = new_fvec(p->source_hopsize);
111
112#ifndef HAVE_THREADS
113  if (aubio_timestretch_queue(p, uri, samplerate)) goto beach;
114  aubio_timestretch_warmup(p);
115#else
116  p->started = 0;
117  p->finish = 0;
118  p->open_thread_running = 0;
119  //p->uri = uri;
120  p->eof = 0;
121  //p->samplerate = samplerate;
122  //if (aubio_timestretch_open(p, uri, samplerate)) goto beach;
123  pthread_mutex_init(&p->open_mutex, 0);
124  pthread_mutex_init(&p->read_mutex, 0);
125  pthread_cond_init (&p->read_avail, 0);
126  pthread_cond_init (&p->read_request, 0);
127  //AUBIO_WRN("timestretch: creating thread\n");
128  pthread_create(&p->read_thread, 0, aubio_timestretch_readfn, p);
129  //AUBIO_DBG("timestretch: new_ waiting for warmup, got %d available\n", p->available);
130  pthread_mutex_lock(&p->read_mutex);
131  aubio_timestretch_queue(p, uri, samplerate);
132#if 0
133  pthread_cond_wait(&p->read_avail, &p->read_mutex);
134  if (!p->opened) {
135    goto beach;
136  }
137#endif
138  pthread_mutex_unlock(&p->read_mutex);
139  //AUBIO_DBG("timestretch: new_ warm up success, got %d available\n", p->available);
140#endif
141
142  return p;
143
144beach:
145  del_aubio_timestretch(p);
146  return NULL;
147}
148
149#define HAVE_OPENTHREAD 1
150//#undef HAVE_OPENTHREAD
151
152uint_t
153aubio_timestretch_queue(aubio_timestretch_t *p, const char_t* uri, uint_t samplerate)
154{
155#ifdef HAVE_THREADS
156#ifdef HAVE_OPENTHREAD
157  if (p->open_thread_running) {
158#if 1
159    if (pthread_cancel(p->open_thread)) {
160      AUBIO_WRN("timestretch: cancelling open thread failed\n");
161      return AUBIO_FAIL;
162    } else {
163      AUBIO_WRN("timestretch: previous open of '%s' cancelled\n", p->uri);
164    }
165    p->open_thread_running = 0;
166#else
167    void *threadfn;
168    if (pthread_join(p->open_thread, &threadfn)) {
169      AUBIO_WRN("timestretch: failed joining existing open thread\n");
170      return AUBIO_FAIL;
171    }
172#endif
173  }
174  //AUBIO_WRN("timestretch: queueing %s\n", uri);
175  //pthread_mutex_lock(&p->read_mutex);
176  p->opened = 0;
177  p->started = 0;
178  p->available = 0;
179  p->uri = uri;
180  p->samplerate = samplerate;
181  //AUBIO_WRN("timestretch: creating thread\n");
182  pthread_create(&p->open_thread, 0, aubio_timestretch_openfn, p);
183#endif
184  //pthread_mutex_unlock(&p->read_mutex);
185  return AUBIO_OK;
186}
187
188uint_t
189aubio_timestretch_open(aubio_timestretch_t *p, const char_t* uri, uint_t samplerate)
190{
191  uint_t err = AUBIO_FAIL;
192  p->available = 0;
193  pthread_mutex_lock(&p->open_mutex);
194  p->open_thread_running = 1;
195#else
196  uint_t err = AUBIO_FAIL;
197#endif
198  p->opened = 0;
199  if (p->source) del_aubio_source(p->source);
200  p->source = new_aubio_source(uri, samplerate, p->source_hopsize);
201  if (!p->source) goto fail;
202  p->uri = uri;
203  p->samplerate = aubio_source_get_samplerate(p->source);
204  p->eof = 0;
205
206  if (p->rb == NULL) {
207    AUBIO_WRN("timestretch: creating with stretch: %.2f pitchscale: %.2f\n",
208        p->stretchratio, p->pitchscale);
209    p->rb = rubberband_new(p->samplerate, 1, p->rboptions, p->stretchratio, p->pitchscale);
210    //rubberband_set_debug_level(p->rb, 10);
211    rubberband_set_max_process_size(p->rb, p->source_hopsize);
212  } else {
213    if (samplerate != p->samplerate) {
214      AUBIO_WRN("timestretch: samplerate change requested, but not implemented\n");
215    }
216    rubberband_reset(p->rb);
217  }
218  p->opened = 1;
219  err = AUBIO_OK;
220  goto unlock;
221fail:
222  p->opened = 2;
223  AUBIO_ERR("timestretch: opening %s failed\n", uri);
224unlock:
225#ifdef HAVE_THREADS
226  p->open_thread_running = 0;
227  pthread_mutex_unlock(&p->open_mutex);
228  //AUBIO_WRN("timestretch: failed opening %s at %dHz\n", uri, samplerate);
229#endif
230  return err;
231}
232
233#ifdef HAVE_THREADS
234void *
235aubio_timestretch_openfn(void *z) {
236  aubio_timestretch_t *p = z;
237  int oldtype;
238  pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
239  //AUBIO_WRN("timestretch: creating thread\n");
240  void *ret;
241  uint_t err = aubio_timestretch_open(p, p->uri, p->samplerate);
242  ret = &err;
243  pthread_exit(ret);
244}
245#endif
246
247uint_t
248aubio_timestretch_get_opened(aubio_timestretch_t *p)
249{
250  if (p == NULL) return 0;
251  else return p->opened;
252}
253
254#ifdef HAVE_THREADS
255void *
256aubio_timestretch_readfn(void *z)
257{
258  aubio_timestretch_t *p = z;
259  //AUBIO_WRN("timestretch: entering thread with %s at %dHz\n", p->uri, p->samplerate);
260  while(1) { //p->available < (int)p->hopsize && p->eof != 1) {
261    //AUBIO_WRN("timestretch: locking in readfn\n");
262    pthread_mutex_lock(&p->read_mutex);
263#if 1
264    if (p->opened == 2) {
265      pthread_cond_signal(&p->read_avail);
266    } else
267    if (p->opened == 0) {
268#ifdef HAVE_OPENTHREAD
269      //(!aubio_timestretch_open(p, p->uri, p->samplerate)) {
270      void * threadfn;
271      if (p->open_thread_running && pthread_join(p->open_thread, &threadfn)) {
272        AUBIO_WRN("timestretch: failed to join opening thread %s at %dHz in thread "
273            "(opened: %d, playing: %d, eof: %d)\n",
274            p->uri, p->samplerate, p->opened, p->started, p->eof);
275      }
276#else
277      //AUBIO_WRN("timestretch: opening source %s\n", p->uri);
278      if (!aubio_timestretch_open(p, p->uri, p->samplerate)) {
279        AUBIO_WRN("timestretch: opened %s at %dHz in thread "
280            "(opened: %d, playing: %d, eof: %d)\n",
281            p->uri, p->samplerate, p->opened, p->started, p->eof);
282        //pthread_cond_signal(&p->read_avail);
283      } else {
284        AUBIO_WRN("timestretch: failed opening %s, exiting thread\n", p->uri);
285        //pthread_cond_signal(&p->read_avail);
286        //pthread_mutex_unlock(&p->read_mutex);
287        //goto end;
288      }
289#endif
290    } else
291    if (!p->started && !p->eof) {
292#endif
293      // fetch the first few samples and mark as started
294      aubio_timestretch_warmup(p);
295      pthread_cond_signal(&p->read_avail);
296      //pthread_cond_wait(&p->read_request, &p->read_mutex);
297      p->started = 1;
298    } else if (!p->eof) {
299      // fetch at least p->hopsize stretched samples
300      p->available = aubio_timestretch_fetch(p, p->hopsize);
301      // signal available frames
302      pthread_cond_signal(&p->read_avail);
303      if (p->eof != 1) {
304        // the end of file was not reached yet, wait for the next read_request
305        pthread_cond_wait(&p->read_request, &p->read_mutex);
306      } else {
307        // eof was reached, do not wait for a read request and mark as stopped
308        p->started = 0;
309      }
310    } else {
311      //pthread_cond_signal(&p->read_avail);
312      pthread_cond_wait(&p->read_request, &p->read_mutex);
313      //AUBIO_WRN("timestretch: finished idle in readfn\n");
314      if (p->finish) pthread_exit(NULL);
315    }
316    //AUBIO_WRN("timestretch: unlocking in readfn\n");
317    pthread_mutex_unlock(&p->read_mutex);
318  }
319end:
320  //AUBIO_WRN("timestretch: exiting readfn\n");
321  pthread_exit(NULL);
322}
323#endif
324
325static void
326aubio_timestretch_warmup (aubio_timestretch_t * p)
327{
328  // warm up rubber band
329  //AUBIO_WRN("timestretch: warming-up\n");
330  unsigned int latency = MAX(p->hopsize, rubberband_get_latency(p->rb));
331#ifdef HAVE_THREADS
332  p->available = aubio_timestretch_fetch(p, latency);
333#else
334  aubio_timestretch_fetch(p, latency);
335#endif
336  //AUBIO_WRN("timestretch: warmup got %d\n", latency);
337}
338
339void
340del_aubio_timestretch (aubio_timestretch_t * p)
341{
342#ifdef HAVE_THREADS
343  void *threadfn;
344  //AUBIO_WRN("timestretch: entering delete\n");
345  if (p->open_thread_running) {
346    if (pthread_cancel(p->open_thread)) {
347      AUBIO_WRN("timestretch: cancelling open thread failed\n");
348    }
349    if (pthread_join(p->open_thread, &threadfn)) {
350      AUBIO_WRN("timestretch: joining open thread failed\n");
351    }
352  }
353  if (!p->opened) goto cleanup;
354  pthread_mutex_lock(&p->read_mutex);
355  p->finish = 1;
356  pthread_cond_signal(&p->read_request);
357  //pthread_cond_wait(&p->read_avail, &p->read_mutex);
358  pthread_mutex_unlock(&p->read_mutex);
359  if ((p->eof == 0) && (pthread_cancel(p->read_thread))) {
360    AUBIO_WRN("timestretch: cancelling thread failed\n");
361  }
362  if (pthread_join(p->read_thread, &threadfn)) {
363    AUBIO_WRN("timestretch: joining thread failed\n");
364  }
365  pthread_mutex_destroy(&p->read_mutex);
366  pthread_cond_destroy(&p->read_avail);
367  pthread_cond_destroy(&p->read_request);
368cleanup:
369#endif
370  if (p->in) del_fvec(p->in);
371  if (p->source) del_aubio_source(p->source);
372  if (p->rb) {
373    rubberband_delete(p->rb);
374  }
375  AUBIO_FREE (p);
376}
377
378uint_t
379aubio_timestretch_get_samplerate (aubio_timestretch_t * p)
380{
381  return p->samplerate;
382}
383
384uint_t aubio_timestretch_get_latency (aubio_timestretch_t * p) {
385  return rubberband_get_latency(p->rb);
386}
387
388uint_t
389aubio_timestretch_set_stretch (aubio_timestretch_t * p, smpl_t stretch)
390{
391  if (!p->rb) {
392    AUBIO_WRN("timestretch: could not set stretch ratio, rubberband not created\n");
393    return AUBIO_FAIL;
394  }
395  if (stretch >= MIN_STRETCH_RATIO && stretch <= MAX_STRETCH_RATIO) {
396    p->stretchratio = stretch;
397    rubberband_set_time_ratio(p->rb, 1./p->stretchratio);
398    return AUBIO_OK;
399  } else {
400    AUBIO_WRN("timestretch: could not set stretch ratio to %.2f\n", stretch);
401    return AUBIO_FAIL;
402  }
403}
404
405smpl_t
406aubio_timestretch_get_stretch (aubio_timestretch_t * p)
407{
408  return p->stretchratio;
409}
410
411uint_t
412aubio_timestretch_set_pitchscale (aubio_timestretch_t * p, smpl_t pitchscale)
413{
414  if (!p->rb) {
415    AUBIO_WRN("timestretch: could not set pitch scale, rubberband not created\n");
416    return AUBIO_FAIL;
417  }
418  if (pitchscale >= 0.0625  && pitchscale <= 4.) {
419    p->pitchscale = pitchscale;
420    rubberband_set_pitch_scale(p->rb, p->pitchscale);
421    return AUBIO_OK;
422  } else {
423    AUBIO_WRN("timestretch: could not set pitchscale to %.2f\n", pitchscale);
424    return AUBIO_FAIL;
425  }
426}
427
428smpl_t
429aubio_timestretch_get_pitchscale (aubio_timestretch_t * p)
430{
431  return p->pitchscale;
432}
433
434uint_t
435aubio_timestretch_set_transpose(aubio_timestretch_t * p, smpl_t transpose)
436{
437  if (transpose >= -24. && transpose <= 24.) {
438    smpl_t pitchscale = POW(2., transpose / 12.);
439    return aubio_timestretch_set_pitchscale(p, pitchscale);
440  } else {
441    AUBIO_WRN("timestretch: could not set transpose to %.2f\n", transpose);
442    return AUBIO_FAIL;
443  }
444}
445
446smpl_t
447aubio_timestretch_get_transpose(aubio_timestretch_t * p)
448{
449  return 12. * LOG(p->pitchscale) / LOG(2.0);
450}
451
452sint_t
453aubio_timestretch_fetch(aubio_timestretch_t *p, uint_t length)
454{
455  uint_t source_read = p->source_hopsize;
456  if (p->source == NULL) {
457    AUBIO_ERR("timestretch: trying to fetch on NULL source\n");
458    return 0;
459  }
460  // read more samples from source until we have enough available or eof is reached
461  int available = rubberband_available(p->rb);
462  while ((available < (int)length) && (p->eof == 0)) {
463    aubio_source_do(p->source, p->in, &source_read);
464    if (source_read < p->source_hopsize) {
465      p->eof = 1;
466    }
467    rubberband_process(p->rb, (const float* const*)&(p->in->data), source_read, p->eof);
468    available = rubberband_available(p->rb);
469  }
470  return available;
471}
472
473void
474aubio_timestretch_do (aubio_timestretch_t * p, fvec_t * out, uint_t * read)
475{
476#ifndef HAVE_THREADS
477  int available = aubio_timestretch_fetch(p, p->hopsize);
478#else /* HAVE_THREADS */
479  int available;
480  pthread_mutex_lock(&p->read_mutex);
481#if 1
482  if (!p->opened) {
483    // this may occur if _do was was called while being opened
484    //AUBIO_WRN("timestretch: calling _do before opening a file\n");
485    pthread_cond_signal(&p->read_request);
486    //available = 0;
487    //pthread_cond_wait(&p->read_avail, &p->read_mutex);
488    available = 0; //p->available;
489  } else
490#endif
491  if (p->eof != 1) {
492    //AUBIO_WRN("timestretch: calling _do after opening a file\n");
493    // signal a read request
494    pthread_cond_signal(&p->read_request);
495    // wait for an available signal
496    pthread_cond_wait(&p->read_avail, &p->read_mutex);
497    available = p->available;
498  } else {
499    available = rubberband_available(p->rb);
500    //AUBIO_WRN("timestretch: reached eof (%d/%d)\n", p->hopsize, available);
501  }
502  pthread_mutex_unlock(&p->read_mutex);
503#endif /* HAVE_THREADS */
504  // now retrieve the samples and write them into out->data
505  if (available >= (int)p->hopsize) {
506    rubberband_retrieve(p->rb, (float* const*)&(out->data), p->hopsize);
507    *read = p->hopsize;
508  } else if (available > 0) {
509    // this occurs each time the end of file is reached
510    //AUBIO_WRN("timestretch: short read\n");
511    rubberband_retrieve(p->rb, (float* const*)&(out->data), available);
512    *read = available;
513  } else {
514    // this may occur if the previous was a short read available == hopsize
515    fvec_zeros(out);
516    *read = 0;
517  }
518#ifdef HAVE_THREADS
519  //pthread_mutex_unlock(&p->read_mutex);
520#endif
521}
522
523uint_t
524aubio_timestretch_seek (aubio_timestretch_t *p, uint_t pos)
525{
526  uint_t err = AUBIO_OK;
527#if HAVE_THREADS
528  if (p == NULL) {
529    AUBIO_WRN("seeking but object not set yet (ignoring)\n");
530    return AUBIO_FAIL;
531  }
532  pthread_mutex_lock(&p->read_mutex);
533  if (p->open_thread_running) {
534    //AUBIO_WRN("seeking but opening thread not completed yet (ignoring)\n");
535    err = AUBIO_OK;
536    goto beach;
537  }
538  if (!p->opened || !p->source) {
539    //AUBIO_WRN("timestretch: seeking but source not opened yet (ignoring)\n");
540    err = AUBIO_OK;
541    goto beach;
542  }
543#endif
544  p->eof = 0;
545  if (p->rb) {
546    rubberband_reset(p->rb);
547  }
548#ifdef HAVE_THREADS
549#ifdef HAVE_OPENTHREAD
550  pthread_mutex_lock(&p->open_mutex);
551#endif
552#endif
553  if (p->source) {
554    err = aubio_source_seek(p->source, pos);
555  } else {
556    AUBIO_WRN("timestretch: seeking but p->source not created?!\n");
557    err = AUBIO_FAIL;
558    goto beach;
559  }
560#if HAVE_THREADS
561  pthread_mutex_unlock(&p->open_mutex);
562  p->available = 0;
563  p->started = 1;
564beach:
565  pthread_mutex_unlock(&p->read_mutex);
566#else
567beach:
568#endif
569  return err;
570}
571
572#endif
Note: See TracBrowser for help on using the repository browser.