summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2015-04-23 15:20:48 +0200
committerBent Bisballe Nyeng <deva@aasimon.org>2016-01-20 13:24:28 +0100
commitb67e30cb862ab640e4a7ced48b1905b2421885b9 (patch)
tree73bc34c7c88a6ba33ca6a9c22ed43cff86376e11
parenteaa2062ea50fea853fa1852b152354ba50d44985 (diff)
Added run sempahore (wait until thread actually started). Fixed localpos bug. Added 'threaded' argument to init that handles events either directly or in a thread when dispatched. Added cachemanager unit test (currently failing).
-rw-r--r--src/cachemanager.cc151
-rw-r--r--src/cachemanager.h13
-rw-r--r--src/drumgizmo.cc14
-rw-r--r--src/drumkitloader.cc3
-rw-r--r--test/Makefile.am29
-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
8 files changed, 272 insertions, 74 deletions
diff --git a/src/cachemanager.cc b/src/cachemanager.cc
index 3984447..e6c43cc 100644
--- a/src/cachemanager.cc
+++ b/src/cachemanager.cc
@@ -48,7 +48,9 @@ static size_t readChunk(std::string filename, int filechannel, size_t pos,
return 0;
}
- if(pos > sf_info.frames) return 0;
+ if(pos > sf_info.frames) {
+ return 0;
+ }
sf_seek(fh, pos, SEEK_SET);
@@ -57,11 +59,11 @@ static size_t readChunk(std::string filename, int filechannel, size_t pos,
sample_t* data = buf;
if(sf_info.channels == 1) {
- size = sf_read_float(fh, data, size);
+ size = sf_readf_float(fh, data, size);
} else {
// check filechannel exists
if(filechannel >= sf_info.channels) {
- filechannel = sf_info.channels - 1;
+ filechannel = sf_info.channels - 1;
}
sample_t buffer[BUFFER_SIZE];
int readsize = BUFFER_SIZE / sf_info.channels;
@@ -89,8 +91,10 @@ CacheManager::~CacheManager()
deinit();
}
-void CacheManager::init(size_t poolsize)
+void CacheManager::init(size_t poolsize, bool threaded)
{
+ this->threaded = threaded;
+
for(size_t i = 0; i < FRAMESIZE; i++) {
nodata[i] = 0;
}
@@ -101,21 +105,26 @@ void CacheManager::init(size_t poolsize)
}
running = true;
- run();
-
- // TODO: Add semaphore
+ if(threaded) {
+ run();
+ sem_run.wait();
+ }
}
void CacheManager::deinit()
{
if(!running) return;
running = false;
- wait_stop();
+ 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)
+sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed,
+ int channel, cacheid_t &id)
{
{
MutexAutolock l(m_ids);
@@ -139,7 +148,11 @@ sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed, int
c.front = new sample_t[CHUNKSIZE];
c.back = new sample_t[CHUNKSIZE];
- memcpy(c.front, c.file->data + c.pos, CHUNKSIZE * sizeof(sample_t));
+ size_t size = CHUNKSIZE;
+ if(size > file->size) size = file->size;
+ memcpy(c.front, c.file->data + c.pos, size * sizeof(sample_t));
+ c.ready = false;
+ //c.pos += size;
// Increase audio ref count
@@ -148,36 +161,17 @@ sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed, int
id2cache[id] = c;
}
+ // Only load next buffer if there are more data in the file to be loaded...
if(initial_samples_needed < file->size) {
cevent_t e =
createLoadNextEvent(c.file, c.channel, c.pos + CHUNKSIZE, c.back);
+ e.ready = &id2cache[id].ready;
pushEvent(e);
}
return file->data; // preloaded data
}
-void CacheManager::close(cacheid_t id)
-{
- if(id == CACHE_DUMMYID) return;
-
- cevent_t e = createCloseEvent(id);
- pushEvent(e);
-
- {
-// event_t e = createEvent(id, CLEAN);
-// MutexAutolock l(m_events);
-// eventqueue.push_front(e);
- }
-
- {
- MutexAutolock l(m_ids);
- availableids.push_back(id);
- }
- // Clean cache_t mapped to event
- // Decrement audiofile ref count
-}
-
sample_t *CacheManager::next(cacheid_t id, size_t &size)
{
size = FRAMESIZE;
@@ -188,8 +182,13 @@ sample_t *CacheManager::next(cacheid_t id, size_t &size)
cache_t& c = id2cache[id];
if(c.localpos < CHUNKSIZE) {
+ sample_t *s = c.front + c.localpos;
c.localpos += size;
- return c.front + c.localpos;
+ return s;
+ }
+
+ if(!c.ready) {
+ printf("\nNOT READY!\n");
}
// Swap buffers
@@ -197,22 +196,46 @@ sample_t *CacheManager::next(cacheid_t id, size_t &size)
c.front = c.back;
c.back = tmp;
- c.localpos = 0;
+ c.localpos = size; // Next time we go here we have already read the first frame.
c.pos += CHUNKSIZE;
if(c.pos < c.file->size) {
- cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos, c.back);
+ cevent_t e = createLoadNextEvent(c.file, c.channel, c.pos + CHUNKSIZE, c.back);
+ c.ready = false;
+ e.ready = &c.ready;
pushEvent(e);
}
return c.front;
}
+void CacheManager::close(cacheid_t id)
+{
+return;
+
+ if(id == CACHE_DUMMYID) return;
+
+ cevent_t e = createCloseEvent(id);
+ pushEvent(e);
+
+ // Clean cache_t mapped to event
+ // Decrement audiofile ref count
+}
+
void CacheManager::handleLoadNextEvent(cevent_t &e)
{
-// memcpy(e.buffer, e.file->data + e.pos, CHUNKSIZE * sizeof(sample_t));
+#if 0 // memcpy
+ size_t size = CHUNKSIZE;
+ 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 * sizeof(sample_t));
readChunk(e.file->filename, e.channel, e.pos, CHUNKSIZE, e.buffer);
+#endif
+ *e.ready = true;
}
void CacheManager::handleCloseEvent(cevent_t &e)
@@ -220,44 +243,66 @@ 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 coutner 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()) {
- cevent_t e = eventqueue.front();
- eventqueue.pop_front();
- m_events.unlock();
-
- // TODO: Skip event if e.pos < cache.pos
-// if(!e.active) continue;
-
- switch(e.cmd) {
- case LOADNEXT:
- handleLoadNextEvent(e);
- break;
- case CLOSE:
- handleCloseEvent(e);
- break;
- }
- } else {
+ 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)
{
- // Check that if event should be merged (Maybe by event queue (ie. push in front).
+ 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();
}
diff --git a/src/cachemanager.h b/src/cachemanager.h
index 25115c3..f2c0122 100644
--- a/src/cachemanager.h
+++ b/src/cachemanager.h
@@ -41,8 +41,9 @@
#define CACHE_DUMMYID -2
#define CACHE_NOID -1
-#define FRAMESIZE 256
-#define CHUNKSIZE FRAMESIZE*100
+#define FRAMESIZE 2048
+#define CHUNKSIZE (FRAMESIZE * 16)
+#define PRELOADSIZE (FRAMESIZE + CHUNKSIZE)
class AudioFile;
typedef int cacheid_t;
@@ -88,7 +89,7 @@ public:
* This method blocks until the thread has been started.
* @param poolsize The maximum number of parellel events supported.
*/
- void init(size_t poolsize);
+ void init(size_t poolsize, bool threaded);
/**
* Stop thread and clean up resources.
@@ -138,6 +139,7 @@ private:
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.
@@ -157,6 +159,7 @@ private:
// For load next event:
size_t pos;
sample_t *buffer;
+ volatile bool *ready;
AudioFile *file;
size_t channel;
} cevent_t;
@@ -168,6 +171,7 @@ private:
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;
@@ -179,8 +183,9 @@ private:
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;
};
diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc
index 2f04488..6c40748 100644
--- a/src/drumgizmo.cc
+++ b/src/drumgizmo.cc
@@ -50,7 +50,7 @@ DrumGizmo::DrumGizmo(AudioOutputEngine *o, AudioInputEngine *i)
loader(), oe(o), ie(i)
{
is_stopping = false;
- cacheManager.init(1000); // start thread
+ cacheManager.init(1000, true); // start thread
}
DrumGizmo::~DrumGizmo()
@@ -415,10 +415,18 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)
{
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;
if(evt->rampdown == NO_RAMPDOWN) {
diff --git a/src/drumkitloader.cc b/src/drumkitloader.cc
index bf01db6..2b66ae0 100644
--- a/src/drumkitloader.cc
+++ b/src/drumkitloader.cc
@@ -30,6 +30,7 @@
#include "drumkitparser.h"
#include "drumgizmo.h"
+#include "cachemanager.h"
DrumKitLoader::DrumKitLoader()
: semaphore("drumkitloader")
@@ -136,7 +137,7 @@ void DrumKitLoader::thread_main()
AudioFile *audiofile = load_queue.front();
load_queue.pop_front();
filename = audiofile->filename;
- audiofile->load();
+ audiofile->load(PRELOADSIZE);
}
loaded++;
diff --git a/test/Makefile.am b/test/Makefile.am
index 2c2ab8d..7dcf0ce 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,7 +1,7 @@
# Rules for the test code (use `make check` to execute)
include $(top_srcdir)/src/Makefile.am.drumgizmo
-TESTS = resource engine cache gui resampler lv2 configfile
+TESTS = resource engine gui resampler lv2 cachemanager configfile
check_PROGRAMS = $(TESTS)
@@ -15,9 +15,23 @@ resource_SOURCES = \
test.cc \
resource_test.cc
+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 = \
@@ -26,17 +40,6 @@ engine_SOURCES = \
test.cc \
engine.cc
-cache_CXXFLAGS = -DOUTPUT=\"cache\" $(CPPUNIT_CFLAGS) \
- -I$(top_srcdir)/src -I$(top_srcdir)/include \
- -I$(top_srcdir)/hugin -DDISABLE_HUGIN
-cache_CFLAGS = -DDISABLE_HUGIN
-cache_LDFLAGS = $(CPPUNIT_LIBS) $(DRUMGIZMO_LIBS) $(PTHREAD_LIBS)
-cache_SOURCES = \
- $(DRUMGIZMO_SOURCES) \
- $(top_srcdir)/hugin/hugin.c \
- test.cc \
- cache.cc
-
gui_CXXFLAGS = -DOUTPUT=\"gui\" $(CPPUNIT_CFLAGS)
gui_LDFLAGS = $(CPPUNIT_LIBS)
gui_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