From b67e30cb862ab640e4a7ced48b1905b2421885b9 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Thu, 23 Apr 2015 15:20:48 +0200 Subject: 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). --- src/cachemanager.cc | 151 +++++++++++++++++++++++++-------------- src/cachemanager.h | 13 ++-- src/drumgizmo.cc | 14 +++- src/drumkitloader.cc | 3 +- test/Makefile.am | 29 ++++---- test/cachemanagertest.cc | 136 +++++++++++++++++++++++++++++++++++ test/kit/ride-multi-channel.wav | Bin 0 -> 28591492 bytes test/kit/ride-single-channel.wav | Bin 0 -> 694144 bytes 8 files changed, 272 insertions(+), 74 deletions(-) create mode 100644 test/cachemanagertest.cc create mode 100644 test/kit/ride-multi-channel.wav create mode 100644 test/kit/ride-single-channel.wav 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 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 + +#include +#include + +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 Binary files /dev/null and b/test/kit/ride-multi-channel.wav differ diff --git a/test/kit/ride-single-channel.wav b/test/kit/ride-single-channel.wav new file mode 100644 index 0000000..1760697 Binary files /dev/null and b/test/kit/ride-single-channel.wav differ -- cgit v1.2.3