summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--drumgizmo/drumgizmoc.cc19
-rw-r--r--drumgizmo/output/alsa/alsa.cc12
m---------hugin0
-rw-r--r--lv2/lv2.cc34
-rw-r--r--src/Makefile.am4
-rw-r--r--src/Makefile.am.drumgizmo3
-rw-r--r--src/audiofile.cc20
-rw-r--r--src/audiofile.h5
-rw-r--r--src/audiooutputengine.h5
-rw-r--r--src/cachemanager.cc341
-rw-r--r--src/cachemanager.h195
-rw-r--r--src/drumgizmo.cc106
-rw-r--r--src/drumgizmo.h19
-rw-r--r--src/drumkitloader.cc13
-rw-r--r--src/drumkitloader.h5
-rw-r--r--src/events.h6
-rw-r--r--src/semaphore.cc8
-rw-r--r--test/Makefile.am18
-rw-r--r--test/cachemanagertest.cc136
-rw-r--r--test/kit/ride-multi-channel.wavbin0 -> 28591492 bytes
-rw-r--r--test/kit/ride-single-channel.wavbin0 -> 694144 bytes
-rw-r--r--vst/drumgizmo_vst.cc1
23 files changed, 867 insertions, 92 deletions
diff --git a/.gitignore b/.gitignore
index 4cfbcc4..dcaeccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/lv2/lv2.cc b/lv2/lv2.cc
index d87665d..687f989 100644
--- a/lv2/lv2.cc
+++ b/lv2/lv2.cc
@@ -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.wav
new file mode 100644
index 0000000..3dec8a9
--- /dev/null
+++ b/test/kit/ride-multi-channel.wav
Binary files differ
diff --git a/test/kit/ride-single-channel.wav b/test/kit/ride-single-channel.wav
new file mode 100644
index 0000000..1760697
--- /dev/null
+++ b/test/kit/ride-single-channel.wav
Binary files differ
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);