diff options
| -rw-r--r-- | .gitignore | 9 | ||||
| -rw-r--r-- | drumgizmo/drumgizmoc.cc | 19 | ||||
| -rw-r--r-- | drumgizmo/output/alsa/alsa.cc | 12 | ||||
| m--------- | hugin | 0 | ||||
| -rw-r--r-- | lv2/lv2.cc | 34 | ||||
| -rw-r--r-- | src/Makefile.am | 4 | ||||
| -rw-r--r-- | src/Makefile.am.drumgizmo | 3 | ||||
| -rw-r--r-- | src/audiofile.cc | 20 | ||||
| -rw-r--r-- | src/audiofile.h | 5 | ||||
| -rw-r--r-- | src/audiooutputengine.h | 5 | ||||
| -rw-r--r-- | src/cachemanager.cc | 341 | ||||
| -rw-r--r-- | src/cachemanager.h | 195 | ||||
| -rw-r--r-- | src/drumgizmo.cc | 106 | ||||
| -rw-r--r-- | src/drumgizmo.h | 19 | ||||
| -rw-r--r-- | src/drumkitloader.cc | 13 | ||||
| -rw-r--r-- | src/drumkitloader.h | 5 | ||||
| -rw-r--r-- | src/events.h | 6 | ||||
| -rw-r--r-- | src/semaphore.cc | 8 | ||||
| -rw-r--r-- | test/Makefile.am | 18 | ||||
| -rw-r--r-- | test/cachemanagertest.cc | 136 | ||||
| -rw-r--r-- | test/kit/ride-multi-channel.wav | bin | 0 -> 28591492 bytes | |||
| -rw-r--r-- | test/kit/ride-single-channel.wav | bin | 0 -> 694144 bytes | |||
| -rw-r--r-- | vst/drumgizmo_vst.cc | 1 | 
23 files changed, 867 insertions, 92 deletions
| @@ -2,9 +2,10 @@ Makefile  Makefile.in  aclocal.m4  autom4te.cache/ +compile  config.guess  config.h -config.h.in +config.h.in*  config.log  config.status  config.sub @@ -16,6 +17,7 @@ libtool  ltmain.sh  missing  stamp-h1 +test-driver  *.o  *.a  *.la @@ -31,4 +33,7 @@ plugingui/rcgen  test/result_*.xml  test/resampler  test/engine -test/gui
\ No newline at end of file +test/gui +drumgizmo-*.tar.gz +tst +vst/Makefile.mingw32
\ No newline at end of file diff --git a/drumgizmo/drumgizmoc.cc b/drumgizmo/drumgizmoc.cc index d6fea9e..612973f 100644 --- a/drumgizmo/drumgizmoc.cc +++ b/drumgizmo/drumgizmoc.cc @@ -322,6 +322,8 @@ int CliMain::run(int argc, char *argv[])    DrumGizmo gizmo(oe, ie); +  gizmo.setFrameSize(oe->getBufferSize()); +    if(kitfile == "" || !gizmo.loadkit(kitfile)) {      printf("Failed to load \"%s\".\n", kitfile.c_str());      return 1; @@ -349,7 +351,22 @@ int CliMain::run(int argc, char *argv[])      return 1;    } -  gizmo.run(endpos); +  size_t pos = 0; +  size_t nsamples = oe->getBufferSize(); +  sample_t *samples = (sample_t *)malloc(nsamples * sizeof(sample_t)); + +  ie->start(); +  oe->start(); + +  while(gizmo.run(pos, samples, nsamples) == true) { +    pos += nsamples; +    if(endpos != -1 && pos >= (size_t)endpos) break; +  } + +  ie->stop(); +  oe->stop(); + +  free(samples);    printf("Quit.\n"); fflush(stdout); diff --git a/drumgizmo/output/alsa/alsa.cc b/drumgizmo/output/alsa/alsa.cc index dc2ac73..0de3cdb 100644 --- a/drumgizmo/output/alsa/alsa.cc +++ b/drumgizmo/output/alsa/alsa.cc @@ -49,6 +49,7 @@ public:    void pre(size_t size);    void run(int channel, sample_t* data, size_t size);    void post(size_t size); +  size_t bufsize();    size_t samplerate();  private: @@ -158,6 +159,11 @@ void Alsa::post(size_t size)    snd_pcm_writei(handle, data, size);  } +size_t Alsa::bufsize() +{ +  return frames; +} +  size_t Alsa::samplerate()  {    return srate; @@ -217,6 +223,12 @@ extern "C" {      alsa->post(s);    } +  size_t bufsize(void *h) +  { +    Alsa *alsa = (Alsa*)h; +    return alsa->bufsize(); +  } +    size_t samplerate(void *h)    {      Alsa *alsa = (Alsa*)h; diff --git a/hugin b/hugin -Subproject bb7388b685ed043b4a3030da86f7f1e49141477 +Subproject 7e734710be0098ea77ca2d3f54fb626b65bbf47 @@ -47,12 +47,11 @@ static DrumGizmo *dg_get_pci(LV2_Handle instance)    return dglv2->dg;  } -LV2_State_Status -dg_save(LV2_Handle                 instance, -        LV2_State_Store_Function   store, -        LV2_State_Handle           handle, -        uint32_t                   flags, -        const LV2_Feature *const * features) +LV2_State_Status dg_save(LV2_Handle instance, +                         LV2_State_Store_Function store, +                         LV2_State_Handle handle, +                         uint32_t flags, +                         const LV2_Feature *const * features)  {    DGLV2 *dglv2 = (DGLV2 *)instance; @@ -77,12 +76,11 @@ dg_save(LV2_Handle                 instance,    return LV2_STATE_SUCCESS;  } -LV2_State_Status -dg_restore(LV2_Handle                  instance, -           LV2_State_Retrieve_Function retrieve, -           LV2_State_Handle            handle, -           uint32_t                    flags, -           const LV2_Feature *const *  features) +LV2_State_Status dg_restore(LV2_Handle instance, +                            LV2_State_Retrieve_Function retrieve, +                            LV2_State_Handle handle, +                            uint32_t flags, +                            const LV2_Feature *const * features)  {    DGLV2 *dglv2 = (DGLV2 *)instance; @@ -148,9 +146,7 @@ LV2_Handle instantiate(const struct _LV2_Descriptor *descriptor,    return (LV2_Handle)dglv2;  } -void connect_port(LV2_Handle instance, -                  uint32_t port, -                  void *data_location) +void connect_port(LV2_Handle instance, uint32_t port, void *data_location)  {    DGLV2 *dglv2 = (DGLV2 *)instance; @@ -171,12 +167,15 @@ void activate(LV2_Handle instance)    (void)dglv2;  } -void run(LV2_Handle instance, -         uint32_t sample_count) +void run(LV2_Handle instance, uint32_t sample_count)  {    static size_t pos = 0;    DGLV2 *dglv2 = (DGLV2 *)instance; +  if(dglv2->buffer_size != sample_count) { +    dglv2->buffer_size = sample_count; +    dglv2->dg->setFrameSize(sample_count); +  }    dglv2->dg->run(pos, dglv2->buffer, sample_count);    pos += sample_count; @@ -184,7 +183,6 @@ void run(LV2_Handle instance,  void deactivate(LV2_Handle instance)  { -  // We don't really need to do anything here.    DGLV2 *dglv2 = (DGLV2 *)instance;    dglv2->dg->stop();  } diff --git a/src/Makefile.am b/src/Makefile.am index df9f4ca..cb44909 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ EXTRA_DIST = \  	channelmixer.h \  	chresampler.h \  	configuration.h \ +	cachemanager.h \  	configparser.h \  	drumgizmo.h \  	drumkit.h \ @@ -39,6 +40,7 @@ EXTRA_DIST = \  	audioinputenginemidi.cc \  	audiooutputengine.cc \  	beatmapper.cc \ +	cachemanager.cc \  	channel.cc \  	channelmixer.cc \  	chresampler.cc \ @@ -63,4 +65,4 @@ EXTRA_DIST = \  	semaphore.cc \  	thread.cc \  	velocity.cc \ -	versionstr.cc
\ No newline at end of file +	versionstr.cc diff --git a/src/Makefile.am.drumgizmo b/src/Makefile.am.drumgizmo index 1a3c857..ae50497 100644 --- a/src/Makefile.am.drumgizmo +++ b/src/Makefile.am.drumgizmo @@ -4,6 +4,7 @@ DRUMGIZMO_SOURCES = \  	$(top_srcdir)/src/channel.cc \  	$(top_srcdir)/src/channelmixer.cc \  	$(top_srcdir)/src/chresampler.cc \ +	$(top_srcdir)/src/cachemanager.cc \  	$(top_srcdir)/src/configfile.cc \  	$(top_srcdir)/src/configuration.cc \  	$(top_srcdir)/src/configparser.cc \ @@ -28,4 +29,4 @@ DRUMGIZMO_SOURCES = \  	$(top_srcdir)/src/velocity.cc \  	$(top_srcdir)/src/versionstr.cc -DRUMGIZMO_LIBS = $(ZITA_LIBS) $(SNDFILE_LIBS) $(EXPAT_LIBS) $(SAMPLERATE_LIBS)
\ No newline at end of file +DRUMGIZMO_LIBS = $(ZITA_LIBS) $(SNDFILE_LIBS) $(EXPAT_LIBS) $(SAMPLERATE_LIBS) diff --git a/src/audiofile.cc b/src/audiofile.cc index 59e0c14..7ab21a9 100644 --- a/src/audiofile.cc +++ b/src/audiofile.cc @@ -115,20 +115,20 @@ void AudioFile::load(int num_samples)    }    size = sf_info.frames; +  preloadedsize = sf_info.frames;    double ratio = (double)Conf::samplerate / (double)sf_info.samplerate;    if(num_samples != ALL_SAMPLES) {      // Make sure we read enough samples, even after conversion.      num_samples /= ratio; -    if((int)size > num_samples) size = num_samples; +    if((int)preloadedsize > num_samples) preloadedsize = num_samples;    } -  sample_t* data = new sample_t[size];  +  sample_t* data = new sample_t[preloadedsize];    if(sf_info.channels == 1) { -    size = sf_read_float(fh, data, size); -  } -  else { +    preloadedsize = sf_read_float(fh, data, preloadedsize); +  } else {      // check filechannel exists      if(filechannel >= sf_info.channels) {          filechannel = sf_info.channels - 1; @@ -139,15 +139,17 @@ void AudioFile::load(int num_samples)      int read;      do {        read = sf_readf_float(fh, buffer, readsize); -      for (int i = 0; i < read; i++) { +      for (int i = 0; i < read && totalread < num_samples; i++) {          data[totalread++] = buffer[i * sf_info.channels + filechannel];        } -    } while(read > 0 && totalread < (int)size); +    } while( (read > 0) && +             (totalread < (int)preloadedsize) && +             (totalread < num_samples) );      // set data size to total bytes read -    size = totalread; +    preloadedsize = totalread;    } -  DEBUG(audiofile,"Loaded %d samples %p\n", (int)size, this); +  DEBUG(audiofile,"Loaded %d samples %p\n", (int)preloadedsize, this);    sf_close(fh); diff --git a/src/audiofile.h b/src/audiofile.h index 98bf101..5f93584 100644 --- a/src/audiofile.h +++ b/src/audiofile.h @@ -79,8 +79,9 @@ public:    bool isLoaded(); -  volatile size_t size; -  volatile sample_t *data; +  volatile size_t size; // Full size of the file +  volatile size_t preloadedsize; // Number of samples preloaded (in data) +  sample_t *data;    std::string filename; diff --git a/src/audiooutputengine.h b/src/audiooutputengine.h index 7f15e49..8b2b768 100644 --- a/src/audiooutputengine.h +++ b/src/audiooutputengine.h @@ -50,11 +50,6 @@ public:    // Reimplement this if you wish to use internal buffer directly.    virtual sample_t *getBuffer(int ch) { return NULL; } - -  /* -   * Overload this method to force engine to use different buffer size. -   */ -  virtual size_t getBufferSize() { return 1024; }  };  #endif/*__DRUMGIZMO_AUDIOOUTPUTENGINE_H__*/ diff --git a/src/cachemanager.cc b/src/cachemanager.cc new file mode 100644 index 0000000..4053f05 --- /dev/null +++ b/src/cachemanager.cc @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + *            cachemanager.cc + * + *  Fri Apr 10 10:39:24 CEST 2015 + *  Copyright 2015 Jonas Suhr Christensen  + *  jsc@umbraculum.org + ****************************************************************************/ + +/* + *  This file is part of DrumGizmo. + * + *  DrumGizmo is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  DrumGizmo is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with DrumGizmo; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. + */ +#include "cachemanager.h" + +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#include <sndfile.h> + +#include <hugin.hpp> + +#define	BUFFER_SIZE	4092 + +#define CHUNKSIZE(x) (x * CHUNK_MULTIPLIER) + +static size_t readChunk(std::string filename, int filechannel, size_t pos, +                        size_t num_samples, sample_t* buf) +{ +  SF_INFO sf_info; +  SNDFILE *fh = sf_open(filename.c_str(), SFM_READ, &sf_info); +  if(!fh) { +    ERR(audiofile,"SNDFILE Error (%s): %s\n", +        filename.c_str(), sf_strerror(fh)); +    return 0; +  } + +  if(pos > sf_info.frames) { +    return 0; +  } + +  sf_seek(fh, pos, SEEK_SET); + +  size_t size = sf_info.frames - pos; +  if(size > num_samples) size = num_samples; + +  sample_t* data = buf; +  if(sf_info.channels == 1) { +    size = sf_readf_float(fh, data, size); +  } else { +    // check filechannel exists +    if(filechannel >= sf_info.channels) { +      filechannel = sf_info.channels - 1; +    } +    sample_t buffer[BUFFER_SIZE]; +    int readsize = BUFFER_SIZE / sf_info.channels; +    int totalread = 0; +    int read; +    do { +      read = sf_readf_float(fh, buffer, readsize); +      for (int i = 0; i < read && totalread < (int)size; i++) { +        data[totalread++] = buffer[i * sf_info.channels + filechannel]; +      } +    } while(read > 0 && totalread < (int)size && totalread < sf_info.frames); +    // set data size to total bytes read +    size = totalread; +  } + +  sf_close(fh); + +  return size; +} + +CacheManager::CacheManager() +  : framesize(0) +  , nodata(NULL) +{ +} + +CacheManager::~CacheManager() +{ +  deinit(); +  delete[] nodata; +} + +void CacheManager::init(size_t poolsize, bool threaded) +{ +  this->threaded = threaded; + +  id2cache.resize(poolsize); +  for(size_t i = 0; i < poolsize; i++) { +    availableids.push_back(i); +  } + +  running = true; +  if(threaded) { +    run(); +    sem_run.wait(); +  } +} + +void CacheManager::deinit() +{ +  if(!running) return; +  running = false; +  if(threaded) { +    sem.post(); +    wait_stop(); +  } +} + +// Invariant: initial_samples_needed < preloaded audio data  +// Proposal: preloaded > 2 x CHUNKSIZE? So that we can fill c.front immediatly on open +sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed, +                             int channel, cacheid_t &id) +{ +  { +    MutexAutolock l(m_ids); +    if(availableids.empty()) { +      id = CACHE_DUMMYID; +    } else { +      id = availableids.front(); +      availableids.pop_front(); +    } +  } + +  if(id == CACHE_DUMMYID) { +    assert(nodata); +    return nodata; +  } + +  cache_t c; +  c.file = file; +  c.channel = channel; +  c.pos = initial_samples_needed; +  c.localpos = 0; +  c.front = new sample_t[CHUNKSIZE(framesize)]; +  c.back = new sample_t[CHUNKSIZE(framesize)]; + +  size_t size = CHUNKSIZE(framesize); +  if(size > (file->preloadedsize - c.pos)) size = (file->preloadedsize - c.pos); +  memcpy(c.front, c.file->data + c.pos, size * sizeof(sample_t)); +  c.ready = false; +  c.pos += size; + +  // Increase audio ref count + +  { +    MutexAutolock l(m_ids); +    id2cache[id] = c; +  } + +  // Only load next buffer if there are more data in the file to be loaded... +  if(c.pos < file->size) { +    cevent_t e = +      createLoadNextEvent(c.file, c.channel, c.pos, c.back); +    e.ready = &id2cache[id].ready; +    pushEvent(e); +  } + +  return file->data; // preloaded data +} + +sample_t *CacheManager::next(cacheid_t id, size_t &size)  +{ +  size = framesize; + +  if(id == CACHE_DUMMYID) { +    assert(nodata); +    return nodata; +  } + +  cache_t& c = id2cache[id]; +  if(c.localpos < CHUNKSIZE(framesize)) { +    sample_t *s = c.front + c.localpos; +    c.localpos += size; +    return s; +  } + +  if(!c.ready) { +    //printf("#%d: NOT READY!\n", id); // TODO: Count and show in UI? +  } + +  // Swap buffers +  sample_t *tmp = c.front; +  c.front = c.back; +  c.back = tmp; + +  c.localpos = size; // Next time we go here we have already read the first frame. + +  c.pos += CHUNKSIZE(framesize); +   +  if(c.pos < c.file->size) { +    cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos, c.back); +    c.ready = false; +    e.ready = &c.ready; +    pushEvent(e); +  }  + +  return c.front; +} + +void CacheManager::close(cacheid_t id) +{ +  if(id == CACHE_DUMMYID) { +    return; +  } + +  cevent_t e = createCloseEvent(id); +  pushEvent(e); +} + +void CacheManager::setFrameSize(size_t framesize) +{ +  this->framesize = framesize; +  delete[] nodata; +  nodata = new sample_t[framesize]; + +  for(size_t i = 0; i < framesize; i++) { +    nodata[i] = 0; +  } +} + +void CacheManager::handleLoadNextEvent(cevent_t &e)  +{ +#if 0 // memcpy +  size_t size = CHUNKSIZE(framesize); +  if(size > (e.file->size - e.pos)) { +    size = (e.file->size - e.pos); +  } +  memcpy(e.buffer, e.file->data + e.pos, size * sizeof(sample_t)); +#elif 1 // diskread +  //memset(e.buffer, 0, CHUNKSIZE(framesize) * sizeof(sample_t)); +  readChunk(e.file->filename, e.channel, e.pos, CHUNKSIZE(framesize), e.buffer); +#endif +  *e.ready = true; +} + +void CacheManager::handleCloseEvent(cevent_t &e)  +{ +  cache_t& c = id2cache[e.id]; +  delete[] c.front; +  delete[] c.back; + +  { +    MutexAutolock l(m_ids); +    availableids.push_back(e.id); +  } + +  // TODO: Count down ref counter on c.file and close it if 0. +} + + +void CacheManager::handleEvent(cevent_t &e) +{ +  switch(e.cmd) { +  case LOADNEXT: +    handleLoadNextEvent(e); +    break; +  case CLOSE: +    handleCloseEvent(e); +    break; +  } +} + +void CacheManager::thread_main() +{ +  sem_run.post(); // Signal that the thread has been started + +  while(running) { +    sem.wait(); + +    m_events.lock(); +    if(eventqueue.empty()) { +      m_events.unlock(); +      continue; +    } + +    cevent_t e = eventqueue.front(); +    eventqueue.pop_front(); +    m_events.unlock(); + +    // TODO: Skip event if e.pos < cache.pos +    // if(!e.active) continue; + +    handleEvent(e); +  } +} + +void CacheManager::pushEvent(cevent_t e) +{ +  if(!threaded) { +    handleEvent(e); +    return; +  } + +  // Check that if event should be merged (Maybe by event queue (ie. push +  // in front). +  { +    MutexAutolock l(m_events); +    eventqueue.push_back(e); +  } + +  sem.post(); +} + +CacheManager::cevent_t +CacheManager::createLoadNextEvent(AudioFile *file, size_t channel, size_t pos, +                                  sample_t* buffer) +{ +  cevent_t e; +  e.cmd = LOADNEXT; +  e.pos = pos; +  e.buffer = buffer; +  e.file = file; +  e.channel = channel; +  return e;  +} + +CacheManager::cevent_t +CacheManager::createCloseEvent(cacheid_t id) +{ +  cevent_t e; +  e.cmd = CLOSE; +  e.id = id; +  return e;  +} diff --git a/src/cachemanager.h b/src/cachemanager.h new file mode 100644 index 0000000..d98d66c --- /dev/null +++ b/src/cachemanager.h @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + *            cachemanager.h + * + *  Fri Apr 10 10:39:24 CEST 2015 + *  Copyright 2015 Jonas Suhr Christensen + *  jsc@umbraculum.org + ****************************************************************************/ + +/* + *  This file is part of DrumGizmo. + * + *  DrumGizmo is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  DrumGizmo is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with DrumGizmo; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. + */ +#ifndef __DRUMGIZMO_CACHEMANAGER_H__ +#define __DRUMGIZMO_CACHEMANAGER_H__ + +#include <string> +#include <list> +#include <vector> + +#include "thread.h" +#include "semaphore.h" +#include "mutex.h" + +#include "audiotypes.h" +#include "audiofile.h" + +#define CACHE_DUMMYID -2 +#define CACHE_NOID -1 + +#define CHUNK_MULTIPLIER 16 + +class AudioFile; +typedef int cacheid_t; + + +//TODO: +// 1: Move nodata initialisation to init method. +// 2: Make semaphore in thread to block init call until thread has been started. + +//// next +// Pre: preloaded contains 2 x framesize. chunk size is framesize. +// allocate 2 chunks and copy initial_samples_needed to first buffer from +// preloaded data and enough to fill up the second buffer from preloaded +// returns the first buffer and its size in &size. +// get id from "free stack" and store pointers to buffers in id vector. +// event: open sndfile handle (if not already open) and increase refcount + +//// next +// Return which ever buffer is the front, swap them and add event to load the +// next chunk. + +//// close +// decrement file handle refcounter and close file if it is 0. +// free the 2 buffers +// (do not erase from the id vector), push index to +// "free stack" for reuse. + +class CacheManager : public Thread { +public: +  /** +   * Empty constructor... +   */ +  CacheManager(); + +  /** +   * Destroy object and stop thread if needed. +   */ +  ~CacheManager(); + +  /** +   * Initialise cache manager and allocate needed resources +   * This method starts the cache manager thread. +   * This method blocks until the thread has been started. +   * @param poolsize The maximum number of parellel events supported.  +   */ +  void init(size_t poolsize, bool threaded); + +  /** +   * Stop thread and clean up resources. +   * This method blocks until the thread has stopped. +   */ +  void deinit(); + +  /** +   * Register new cache entry. +   * Prepares an entry in the cache manager for future disk streaming. +   * @param file A pointer to the file which is to be streamed from. +   * @param initial_samples_needed The number of samples needed in the first +   *  read that is not nessecarily of framesize. This is the number of samples +   *  from the input event offset to the end of the frame in which it resides. +   *  initial_samples_needed <= framesize. +   * @param channel The channel to which the cache id will be bound. +   * @param [out] new_id The newly created cache id. +   * @return A pointer to the first buffer containing the +   *  'initial_samples_needed' number of samples. +   */ +  sample_t *open(AudioFile *file, size_t initial_samples_needed, int channel, +                 cacheid_t &new_id); + +  /** +   * Get next buffer. +   * Returns the next buffer for reading based on cache id. +   * This function will (if needed) schedule a new disk read to make sure that +   * data is available in the next call to this method. +   * @param id The cache id to read from. +   * @param [out] size The size of the returned buffer. +   * @return A pointer to the buffer. +   */ +  sample_t *next(cacheid_t id, size_t &size); + +  /** +   * Unregister cache entry. +   * Close associated file handles and free associated buffers. +   * @param id The cache id to close. +   */ +  void close(cacheid_t id); + +  void setFrameSize(size_t framesize); + +  ///! Internal thread main method - needs to be public. +  void thread_main(); + +private: +  size_t framesize; +  sample_t *nodata; + +  typedef struct { +    AudioFile *file; +    size_t channel; +    size_t pos; //< File possition +    volatile bool ready; +    sample_t *front; +    sample_t *back; +    size_t localpos; //< Intra buffer (front) position. +  } cache_t; + +  typedef enum { +    LOADNEXT = 0, +    CLOSE = 1 +  } cmd_t; + +  typedef struct { +    cmd_t cmd; + +    // For close event: +    cacheid_t id; + +    // For load next event: +    size_t pos; +    sample_t *buffer; +    volatile bool *ready; +    AudioFile *file; +    size_t channel; +  } cevent_t; + +  cevent_t createLoadNextEvent(AudioFile *file, size_t channel, size_t pos, +                               sample_t* buffer); +  cevent_t createCloseEvent(cacheid_t id); + +  void handleLoadNextEvent(cevent_t &e); +  void handleCloseEvent(cevent_t &e); + +  void handleEvent(cevent_t &e); +  void pushEvent(cevent_t e); + +  std::vector<cache_t> id2cache;  + +  // Protected by mutex: +  std::list<cevent_t> eventqueue; +  std::list<cacheid_t> availableids;  +   +  Mutex m_events; +  Mutex m_ids; + +  bool threaded; // Indicates if we are running in thread mode or offline mode. +  Semaphore sem; +  Semaphore sem_run; +  bool running; +}; + +#endif/*__DRUMGIZMO_CACHEMANAGER_H__*/ diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc index 7ce05ef..8661232 100644 --- a/src/drumgizmo.cc +++ b/src/drumgizmo.cc @@ -46,14 +46,19 @@  #include "nolocale.h"  DrumGizmo::DrumGizmo(AudioOutputEngine *o, AudioInputEngine *i) -  : MessageReceiver(MSGRCV_ENGINE), -    loader(), oe(o), ie(i) +  : MessageReceiver(MSGRCV_ENGINE) +  , loader() +  , oe(o) +  , ie(i) +  , framesize(0)  {    is_stopping = false; +  cacheManager.init(1000, true); // start thread  }  DrumGizmo::~DrumGizmo()  { +  cacheManager.deinit(); // stop thread  }  bool DrumGizmo::loadkit(std::string file) @@ -164,8 +169,30 @@ void DrumGizmo::handleMessage(Message *msg)    }  } +void DrumGizmo::setFrameSize(size_t framesize) +{ +  // If we are resampling override the frame size. +  if(resampler[0].ratio() != 1) { +    framesize = RESAMPLER_INPUT_BUFFER; +  } + +  if(this->framesize != framesize) { +    printf("New framesize: %d\n", framesize); + +    this->framesize = framesize; + +    // Update framesize in drumkitloader and cachemanager: +    loader.setFrameSize(framesize); +    printf("loader.setFrameSize\n"); fflush(stdout); +    cacheManager.setFrameSize(framesize); +    printf("cacheManager.setFrameSize\n"); fflush(stdout); +  } +} +  bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples)  { +  setFrameSize(nsamples); +    // Handle engine messages, at most one in each iteration:    handleMessages(1); @@ -353,26 +380,6 @@ bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples)    return true;  } -void DrumGizmo::run(int endpos) -{ -  size_t pos = 0; -  size_t nsamples = oe->getBufferSize(); -  sample_t *samples = (sample_t *)malloc(nsamples * sizeof(sample_t)); - -  ie->start(); -  oe->start(); - -  while(run(pos, samples, nsamples) == true) { -    pos += nsamples; -    if(endpos != -1 && pos >= (size_t)endpos) break; -  } - -  ie->stop(); -  oe->stop(); - -  free(samples); -} -  #ifdef SSE  #define N 8  typedef float vNsf __attribute__ ((vector_size(sizeof(float)*N))); @@ -381,7 +388,7 @@ typedef float vNsf __attribute__ ((vector_size(sizeof(float)*N)));  void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)  {    std::list< Event* >::iterator i = activeevents[ch].begin(); -  while(i != activeevents[ch].end()) { +  for(; i != activeevents[ch].end(); ++i) {      bool removeevent = false;      Event *event = *i; @@ -398,43 +405,68 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)            break;          } +        // Don't handle event now is is scheduled for a future iteration? +        if(evt->offset > (pos + sz)) { +          continue; +        } + +        if(evt->cache_id == CACHE_NOID) { +          size_t initial_chunksize = (pos + sz) - evt->offset; +          evt->buffer = +            cacheManager.open(af, initial_chunksize, ch, evt->cache_id); +          evt->buffer_size = initial_chunksize; +        } +          {          MutexAutolock l(af->mutex); -        size_t n = 0; +        size_t n = 0; // default start point is 0. + +        // If we are not at offset 0 in current buffer:          if(evt->offset > (size_t)pos) n = evt->offset - pos; -        size_t end = sz; + +        size_t end = sz; // default end point is the end of the buffer. + +        // Find the end point intra-buffer          if((evt->t + end - n) > af->size) end = af->size - evt->t + n; + +        // This should not be necessary but make absolutely shure that we do +        // not write over the end of the buffer.          if(end > sz) end = sz; +        size_t t = 0; // Internal buffer counter          if(evt->rampdown == NO_RAMPDOWN) {  #ifdef SSE -//          DEBUG(drumgizmo,"%d\n", evt->t); fflush(stdout); -         size_t optend = ((end - n) / N) * N + n; -         for(; n < optend; n += N) { -            *(vNsf*)&(s[n]) += *(vNsf*)&(af->data[evt->t]); -            evt->t += N; -          } +          size_t optend = ((end - n) / N) * N + n; +          for(; n < optend; n += N) { +            *(vNsf*)&(s[n]) += *(vNsf*)&(evt->buffer[t]); +            t += N; +         }  #endif            for(; n < end; n++) { -            s[n] += af->data[evt->t]; -            evt->t++; +            s[n] += evt->buffer[t]; +            t++;            }          } else { // Ramp down in progress.            for(; n < end && evt->rampdown; n++) {              float scale = (float)evt->rampdown/(float)evt->ramp_start; -            s[n] += af->data[evt->t] * scale; -            evt->t++; +            s[n] += evt->buffer[t] * scale; +            t++;              evt->rampdown--;            }            if(evt->rampdown == 0) {              removeevent = true; // Down ramp done. Remove event. +            cacheManager.close(evt->cache_id);            }          } +        evt->t += t; // Add internal buffer counter to "global" event counter.          if(evt->t >= af->size) {             removeevent = true; +          cacheManager.close(evt->cache_id); +        } else { +          evt->buffer = cacheManager.next(evt->cache_id, evt->buffer_size);          }          } @@ -447,7 +479,6 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)        i = activeevents[ch].erase(i);        continue;      } -    i++;    }  } @@ -468,6 +499,9 @@ void DrumGizmo::setSamplerate(int samplerate)    for(int i = 0; i < MAX_NUM_CHANNELS; i++) {      resampler[i].setup(kit.samplerate(), Conf::samplerate);    } +  if(resampler[0].ratio() != 1) { +    setFrameSize(RESAMPLER_INPUT_BUFFER); +  }  #endif/*WITH_RESAMPLER*/  } diff --git a/src/drumgizmo.h b/src/drumgizmo.h index 5e58ba5..4c0740e 100644 --- a/src/drumgizmo.h +++ b/src/drumgizmo.h @@ -24,8 +24,7 @@   *  along with DrumGizmo; if not, write to the Free Software   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.   */ -#ifndef __DRUMGIZMO_DRUMGIZMO_H__ -#define __DRUMGIZMO_DRUMGIZMO_H__ +#pragma once  #include <string>  #include <list> @@ -38,6 +37,7 @@  #include "drumkit.h"  #include "drumkitloader.h" +#include "cachemanager.h"  #include "mutex.h" @@ -51,6 +51,7 @@  #define MAX_NUM_CHANNELS 64  #define REFSFILE "refs.conf" +#define RESAMPLER_INPUT_BUFFER 64  class DrumGizmo : public MessageReceiver {  public: @@ -61,10 +62,6 @@ public:    bool init(); -  /** -   * @param endpos number of samples to process, -1 := never stop. -   */ -  void run(int endpos);    bool run(size_t pos, sample_t *samples, size_t nsamples);    void stop(); @@ -78,6 +75,8 @@ public:    int samplerate();    void setSamplerate(int samplerate); +  void setFrameSize(size_t framesize); +  private:    DrumKitLoader loader; @@ -91,12 +90,12 @@ private:    CHResampler resampler[MAX_NUM_CHANNELS];    sample_t resampler_output_buffer[MAX_NUM_CHANNELS][4096]; -  sample_t resampler_input_buffer[MAX_NUM_CHANNELS][64]; +  sample_t resampler_input_buffer[MAX_NUM_CHANNELS][RESAMPLER_INPUT_BUFFER];    std::map<std::string, AudioFile *> audiofiles; +  CacheManager cacheManager;    DrumKit kit; -}; - -#endif/*__DRUMGIZMO_DRUMGIZMO_H__*/ +  size_t framesize; +}; diff --git a/src/drumkitloader.cc b/src/drumkitloader.cc index bf01db6..413d3f4 100644 --- a/src/drumkitloader.cc +++ b/src/drumkitloader.cc @@ -30,9 +30,11 @@  #include "drumkitparser.h"  #include "drumgizmo.h" +#include "cachemanager.h"  DrumKitLoader::DrumKitLoader()    : semaphore("drumkitloader") +  , framesize(0)  {    run();    run_semaphore.wait(); // Wait for the thread to actually start. @@ -63,6 +65,13 @@ void DrumKitLoader::skip()    load_queue.clear();  } +void DrumKitLoader::setFrameSize(size_t framesize) +{ +  MutexAutolock l(mutex); +  this->framesize = framesize; +  framesize_semaphore.post(); // Signal that the framesize has been set. +} +  bool DrumKitLoader::isDone()  {    MutexAutolock l(mutex); @@ -119,6 +128,8 @@ void DrumKitLoader::thread_main()    run_semaphore.post(); // Signal that the thread has been started. +  framesize_semaphore.wait(); // Wait until the framesize has been set. +    while(running) {      size_t size;      { @@ -136,7 +147,7 @@ void DrumKitLoader::thread_main()        AudioFile *audiofile = load_queue.front();        load_queue.pop_front();        filename = audiofile->filename; -      audiofile->load(); +      audiofile->load(framesize * CHUNK_MULTIPLIER + framesize);      }      loaded++; diff --git a/src/drumkitloader.h b/src/drumkitloader.h index 2c0ea8e..550d885 100644 --- a/src/drumkitloader.h +++ b/src/drumkitloader.h @@ -85,15 +85,20 @@ public:     */    void skip(); +  void setFrameSize(size_t framesize); +  private:    Semaphore run_semaphore;    Semaphore semaphore; +  Semaphore framesize_semaphore;    Mutex mutex;    volatile bool running;    std::list<AudioFile*> load_queue;    size_t total_num_audiofiles;    size_t fraction;    size_t loaded; + +  size_t framesize;  };  #endif/*__DRUMGIZMO_DRUMKITLOADER_H__*/ diff --git a/src/events.h b/src/events.h index fa0147b..ea897f1 100644 --- a/src/events.h +++ b/src/events.h @@ -35,6 +35,7 @@  #include "audiofile.h"  #include "audio.h"  #include "mutex.h" +#include "cachemanager.h"  typedef unsigned int timepos_t; @@ -58,6 +59,7 @@ public:    EventSample(channel_t c, float g, AudioFile *af, std::string grp,                void *instr)    { +    cache_id = CACHE_NOID;      channel = c;      gain = g;      t = 0; @@ -70,6 +72,10 @@ public:    Event::type_t type() { return Event::sample; } +  cacheid_t cache_id; +  sample_t *buffer; +  size_t buffer_size; +    float gain;    unsigned int t;    AudioFile *file; diff --git a/src/semaphore.cc b/src/semaphore.cc index 47ce8e0..b478eb1 100644 --- a/src/semaphore.cc +++ b/src/semaphore.cc @@ -46,7 +46,7 @@ struct semaphore_private_t {  Semaphore::Semaphore(const char *name)  {    this->name = name; -  DEBUG(semaphore, "Create [%s]\n", name); +  //  DEBUG(semaphore, "Create [%s]\n", name);    prv = new struct semaphore_private_t(); @@ -62,7 +62,7 @@ Semaphore::Semaphore(const char *name)  Semaphore::~Semaphore()  { -  DEBUG(semaphore, "Delete [%s]\n", name); +  //  DEBUG(semaphore, "Delete [%s]\n", name);  #ifdef WIN32    CloseHandle(prv->semaphore); @@ -75,7 +75,7 @@ Semaphore::~Semaphore()  void Semaphore::post()  { -  DEBUG(semaphore, "Post [%s]\n", name); +  //  DEBUG(semaphore, "Post [%s]\n", name);  #ifdef WIN32    ReleaseSemaphore(prv->semaphore, 1, NULL); @@ -86,7 +86,7 @@ void Semaphore::post()  void Semaphore::wait()  { -  DEBUG(semaphore, "Wait [%s]\n", name); +  //  DEBUG(semaphore, "Wait [%s]\n", name);  #ifdef WIN32    WaitForSingleObject(prv->semaphore, INFINITE); diff --git a/test/Makefile.am b/test/Makefile.am index 90373e1..6526f22 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,13 +1,27 @@  # Rules for the test code (use `make check` to execute)  include $(top_srcdir)/src/Makefile.am.drumgizmo -TESTS = engine gui resampler lv2 configfile +TESTS = engine gui resampler lv2 configfile cachemanager  check_PROGRAMS = $(TESTS) +cachemanager_CXXFLAGS = -DOUTPUT=\"cachemanager\" $(CPPUNIT_CFLAGS) \ +	-I$(top_srcdir)/src -I$(top_srcdir)/include \ +	-I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS) +cachemanager_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS) +cachemanager_SOURCES = \ +	$(top_srcdir)/src/cachemanager.cc \ +	$(top_srcdir)/src/thread.cc \ +	$(top_srcdir)/src/mutex.cc \ +	$(top_srcdir)/src/semaphore.cc \ +	$(top_srcdir)/src/configuration.cc \ +	$(top_srcdir)/src/audiofile.cc \ +	test.cc \ +	cachemanagertest.cc +  engine_CXXFLAGS = -DOUTPUT=\"engine\" $(CPPUNIT_CFLAGS) \  	-I$(top_srcdir)/src -I$(top_srcdir)/include \ -	-I$(top_srcdir)/hugin -DDISABLE_HUGIN +	-I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS)  engine_CFLAGS = -DDISABLE_HUGIN  engine_LDFLAGS = $(CPPUNIT_LIBS) $(DRUMGIZMO_LIBS) $(PTHREAD_LIBS)  engine_SOURCES = \ diff --git a/test/cachemanagertest.cc b/test/cachemanagertest.cc new file mode 100644 index 0000000..ae346db --- /dev/null +++ b/test/cachemanagertest.cc @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + *            cachemanagertest.cc + * + *  Sun Apr 19 10:15:59 CEST 2015 + *  Copyright 2015 Bent Bisballe Nyeng + *  deva@aasimon.org + ****************************************************************************/ + +/* + *  This file is part of DrumGizmo. + * + *  DrumGizmo is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  DrumGizmo is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with DrumGizmo; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. + */ +#include <cppunit/extensions/HelperMacros.h> + +#include <cachemanager.h> +#include <unistd.h> + +class test_cachemanager : public CppUnit::TestFixture +{ +  CPPUNIT_TEST_SUITE(test_cachemanager); +  CPPUNIT_TEST(singlechannel_nonthreaded); +	CPPUNIT_TEST(singlechannel_threaded); +  CPPUNIT_TEST(multichannel_nonthreaded); +  CPPUNIT_TEST(multichannel_threaded); +	CPPUNIT_TEST_SUITE_END(); + +public: +	void setUp() {} +	void tearDown() {} + +  void testit(const char *filename, int channel, bool threaded) +  { + +    // Reference file: +    AudioFile afref(filename, channel); +    printf("afref.load\n"); +    afref.load(ALL_SAMPLES); + +    // Input file: +    AudioFile af(filename, channel); +    printf("af.load\n"); +    af.load(ALL_SAMPLES); +    //af.load(PRELOADSIZE); + +    CacheManager cm; +    printf("cm.init\n"); +    cm.init(100, threaded); + +    cacheid_t id; +    // TODO: test 0 ... FRAMESIZE - 1 +    size_t initial_samples_needed = (FRAMESIZE - 1) / 2; + +    printf("open\n"); +    sample_t *s = cm.open(&af, initial_samples_needed, channel, id); +    size_t size = initial_samples_needed; +    size_t offset = 0; + +    // Test pre cache: +    for(size_t i = 0; i < size; i++) { +      CPPUNIT_ASSERT_EQUAL(afref.data[offset], s[i]); +      offset++; +    } + +    // Test the rest +    while(offset < afref.size) { + +      if(threaded) { +        usleep(1000000.0 / 44100.0 * FRAMESIZE); +        //sleep(1); // sleep 1 second +      } + +      //printf("offset: %d\t", offset); +      s = cm.next(id, size); +      //printf("next -> size: %d\n", size); +      for(size_t i = 0; i < size && (offset < afref.size); i++) { +        CPPUNIT_ASSERT_EQUAL(afref.data[offset], s[i]); +        offset++; +      } +    } + +    printf("done\n"); +	} + +  void singlechannel_nonthreaded() +  { +    const char filename[] = "kit/ride-single-channel.wav"; +    int channel = 0; +    bool threaded = false; +    testit(filename, channel, threaded); +  } + +  void singlechannel_threaded() +  { +    const char filename[] = "kit/ride-single-channel.wav"; +    int channel = 0; +    bool threaded = true; +    testit(filename, channel, threaded); +  } + +  void multichannel_nonthreaded() +  { +    const char filename[] = "kit/ride-multi-channel.wav"; +    int channel = 0; +    bool threaded = false; +    testit(filename, channel, threaded); +  } + +  void multichannel_threaded() +  { +    const char filename[] = "kit/ride-multi-channel.wav"; +    int channel = 0; +    bool threaded = true; +    testit(filename, channel, threaded); +  } + +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(test_cachemanager); + + + diff --git a/test/kit/ride-multi-channel.wav b/test/kit/ride-multi-channel.wavBinary files differ new file mode 100644 index 0000000..3dec8a9 --- /dev/null +++ b/test/kit/ride-multi-channel.wav diff --git a/test/kit/ride-single-channel.wav b/test/kit/ride-single-channel.wavBinary files differ new file mode 100644 index 0000000..1760697 --- /dev/null +++ b/test/kit/ride-single-channel.wav diff --git a/vst/drumgizmo_vst.cc b/vst/drumgizmo_vst.cc index 6aec4f2..1e40852 100644 --- a/vst/drumgizmo_vst.cc +++ b/vst/drumgizmo_vst.cc @@ -450,6 +450,7 @@ void DrumGizmoVst::processReplacing(float** inputs, float** outputs,      if(buffer) free(buffer);      buffer_size = sampleFrames;      buffer = (sample_t*)malloc(sizeof(sample_t) * buffer_size); +    drumgizmo->setFrameSize(buffer_size);    }    drumgizmo->run(pos, buffer, buffer_size); | 
