summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2016-01-19 08:32:44 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2016-01-19 08:32:44 +0100
commit22b99e7d0379d1b347ea04c27e46b83ed40d057b (patch)
tree3b0daf93a9f953cf0b9c9e6ad05dff762b02315b
parent2a9dda465230b3b129dc942095a7c8a99ef5fa50 (diff)
Split CacheManager into several AudioCache classes and refactored the lot of them. Unit tests added.
-rw-r--r--src/Makefile.am.drumgizmo6
-rw-r--r--src/audiocache.cc274
-rw-r--r--src/audiocache.h136
-rw-r--r--src/audiocacheeventhandler.cc315
-rw-r--r--src/audiocacheeventhandler.h109
-rw-r--r--src/audiocachefile.cc (renamed from src/cacheaudiofile.cc)58
-rw-r--r--src/audiocachefile.h95
-rw-r--r--src/audiocacheidmanager.cc128
-rw-r--r--src/audiocacheidmanager.h88
-rw-r--r--src/cachemanager.cc438
-rw-r--r--src/cachemanager.h221
-rw-r--r--src/drumgizmo.cc16
-rw-r--r--src/drumgizmo.h4
-rw-r--r--src/drumkitloader.cc1
-rw-r--r--src/events.h2
-rw-r--r--test/Makefile.am55
-rw-r--r--test/audiocacheeventhandlertest.cc (renamed from src/cacheaudiofile.h)57
-rw-r--r--test/audiocachefiletest.cc189
-rw-r--r--test/audiocacheidmanagertest.cc98
-rw-r--r--test/audiocachetest.cc177
-rw-r--r--test/cachemanagertest.cc149
21 files changed, 1733 insertions, 883 deletions
diff --git a/src/Makefile.am.drumgizmo b/src/Makefile.am.drumgizmo
index 54f830f..57b6362 100644
--- a/src/Makefile.am.drumgizmo
+++ b/src/Makefile.am.drumgizmo
@@ -1,11 +1,13 @@
DRUMGIZMO_SOURCES = \
+ $(top_srcdir)/src/audiocachefile.cc \
+ $(top_srcdir)/src/audiocache.cc \
+ $(top_srcdir)/src/audiocacheeventhandler.cc \
+ $(top_srcdir)/src/audiocacheidmanager.cc \
$(top_srcdir)/src/audioinputenginemidi.cc \
$(top_srcdir)/src/audiofile.cc \
$(top_srcdir)/src/channel.cc \
$(top_srcdir)/src/channelmixer.cc \
$(top_srcdir)/src/chresampler.cc \
- $(top_srcdir)/src/cacheaudiofile.cc \
- $(top_srcdir)/src/cachemanager.cc \
$(top_srcdir)/src/configfile.cc \
$(top_srcdir)/src/configuration.cc \
$(top_srcdir)/src/configparser.cc \
diff --git a/src/audiocache.cc b/src/audiocache.cc
new file mode 100644
index 0000000..1a2225e
--- /dev/null
+++ b/src/audiocache.cc
@@ -0,0 +1,274 @@
+/* -*- 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 "audiocache.h"
+
+#include <mutex>
+
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <hugin.hpp>
+
+#include "audiocachefile.h"
+
+#define CHUNKSIZE(x) (x * CHUNK_MULTIPLIER)
+
+AudioCache::~AudioCache()
+{
+
+ // TODO: Run through all active cacheids and release them/close their files.
+
+ deinit();
+ delete[] nodata;
+}
+
+void AudioCache::init(size_t poolsize)
+{
+ setAsyncMode(true);
+
+ idManager.init(poolsize);
+ eventHandler.start();
+}
+
+void AudioCache::deinit()
+{
+ eventHandler.stop();
+}
+
+// Invariant: initial_samples_needed < preloaded audio data
+sample_t* AudioCache::open(AudioFile* file, size_t initial_samples_needed,
+ int channel, cacheid_t& id)
+{
+ if(!file->isValid())
+ {
+ // File preload not yet ready - skip this sample.
+ id = CACHE_DUMMYID;
+ assert(nodata);
+ return nodata;
+ }
+
+ // Register a new id for this cache session.
+ id = idManager.registerID({});
+
+ // If we are out of availabel ids we get CACHE_DUMMYID
+ if(id == CACHE_DUMMYID)
+ {
+ // Use nodata buffer instead.
+ assert(nodata);
+ return nodata;
+ }
+
+ // Get the cache_t connected with the registered id.
+ cache_t& c = idManager.getCache(id);
+
+ c.afile = &eventHandler.openFile(file->filename);
+ c.channel = channel;
+
+ // Next call to 'next()' will read from this point.
+ c.localpos = initial_samples_needed;
+
+ c.front = nullptr; // This is allocated when needed.
+ c.back = nullptr; // This is allocated when needed.
+
+ // cropped_size is the preload chunk size cropped to sample length.
+ size_t cropped_size = file->preloadedsize - c.localpos;
+ cropped_size /= framesize;
+ cropped_size *= framesize;
+ cropped_size += initial_samples_needed;
+
+ if(file->preloadedsize == file->size)
+ {
+ // We have preloaded the entire file, so use it.
+ cropped_size = file->preloadedsize;
+ }
+
+ c.preloaded_samples = file->data;
+ c.preloaded_samples_size = cropped_size;
+
+ // Next read from disk will read from this point.
+ c.pos = cropped_size;//c.preloaded_samples_size;
+
+ // Only load next buffer if there are more data in the file to be loaded...
+ if(c.pos < file->size)
+ {
+ if(c.back == nullptr)
+ {
+ c.back = new sample_t[CHUNKSIZE(framesize)];
+ }
+
+ eventHandler.pushLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready);
+ }
+
+ return c.preloaded_samples; // return preloaded data
+}
+
+sample_t* AudioCache::next(cacheid_t id, size_t& size)
+{
+ size = framesize;
+
+ if(id == CACHE_DUMMYID)
+ {
+ assert(nodata);
+ return nodata;
+ }
+
+ cache_t& c = idManager.getCache(id);
+
+ if(c.preloaded_samples)
+ {
+
+ // We are playing from memory:
+ if(c.localpos < c.preloaded_samples_size)
+ {
+ sample_t* s = c.preloaded_samples + c.localpos;
+ c.localpos += framesize;
+ return s;
+ }
+
+ c.preloaded_samples = nullptr; // Start using samples from disk.
+
+ }
+ else
+ {
+
+ // We are playing from cache:
+ if(c.localpos < CHUNKSIZE(framesize))
+ {
+ sample_t* s = c.front + c.localpos;
+ c.localpos += framesize;
+ return s;
+ }
+ }
+
+ // Check for buffer underrun
+ if(!c.ready)
+ {
+ // Just return silence.
+ ++numberOfUnderruns;
+ return nodata;
+ }
+
+ // Swap buffers
+ std::swap(c.front, c.back);
+
+ // Next time we go here we have already read the first frame.
+ c.localpos = framesize;
+
+ c.pos += CHUNKSIZE(framesize);
+
+ // Does the file have remaining unread samples?
+ if(c.pos < c.afile->getSize())
+ {
+ // Do we have a back buffer to read into?
+ if(c.back == nullptr)
+ {
+ c.back = new sample_t[CHUNKSIZE(framesize)];
+ }
+
+ eventHandler.pushLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready);
+ }
+
+ // We should always have a front buffer at this point.
+ assert(c.front);
+
+ return c.front;
+}
+
+bool AudioCache::isReady(cacheid_t id)
+{
+ if(id == CACHE_DUMMYID)
+ {
+ return true;
+ }
+
+ cache_t& cache = idManager.getCache(id);
+ return cache.ready;
+}
+
+void AudioCache::close(cacheid_t id)
+{
+ if(id == CACHE_DUMMYID)
+ {
+ return;
+ }
+
+ eventHandler.pushCloseEvent(id);
+}
+
+void AudioCache::setFrameSize(size_t framesize)
+{
+ // Make sure the event handler thread is stalled while we set the framesize
+ // state.
+ std::lock_guard<AudioCacheEventHandler> eventHandlerLock(eventHandler);
+
+ // NOTE: Not threaded...
+ //std::lock_guard<AudioCacheIDManager> idManagerLock(idManager);
+
+ if(framesize > this->framesize)
+ {
+ delete[] nodata;
+ nodata = new sample_t[framesize];
+
+ for(size_t i = 0; i < framesize; ++i)
+ {
+ nodata[i] = 0.0f;
+ }
+ }
+
+ this->framesize = framesize;
+
+ eventHandler.setChunkSize(CHUNKSIZE(framesize));
+}
+
+size_t AudioCache::frameSize() const
+{
+ return framesize;
+}
+
+void AudioCache::setAsyncMode(bool async)
+{
+ // TODO: Clean out read queue.
+ // TODO: Block until reader thread is idle, otherwise we might screw up the
+ // buffers...?
+ eventHandler.setThreaded(async);
+}
+
+bool AudioCache::asyncMode() const
+{
+ return eventHandler.getThreaded();
+}
+
+size_t AudioCache::getNumberOfUnderruns() const
+{
+ return numberOfUnderruns;
+}
+
+void AudioCache::resetNumberOfUnderruns()
+{
+ numberOfUnderruns = 0;
+}
diff --git a/src/audiocache.h b/src/audiocache.h
new file mode 100644
index 0000000..824db3a
--- /dev/null
+++ b/src/audiocache.h
@@ -0,0 +1,136 @@
+/* -*- 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.
+ */
+#pragma once
+
+#include <string>
+#include <list>
+#include <vector>
+
+#include "audiotypes.h"
+#include "audiofile.h"
+
+#include "audiocachefile.h"
+#include "audiocacheidmanager.h"
+#include "audiocacheeventhandler.h"
+
+#define CHUNK_MULTIPLIER 16
+
+
+//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 AudioCache {
+public:
+ AudioCache() = default;
+
+ //! Destroy object and stop thread if needed.
+ ~AudioCache();
+
+ //! 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);
+
+ //! 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);
+
+ //! Returns if the next chunk of the supplied id has been read form disk.
+ bool isReady(cacheid_t id);
+
+ //! Unregister cache entry.
+ //! Close associated file handles and free associated buffers.
+ //! \param id The cache id to close.
+ void close(cacheid_t id);
+
+ //! Set/get internal framesize used when iterating through cache buffers.
+ void setFrameSize(size_t framesize);
+ size_t frameSize() const;
+
+ //! Control/get reader threaded mode.
+ //! True means reading happening threaded, false means all reading done
+ //! synchronious.
+ void setAsyncMode(bool async);
+ bool asyncMode() const;
+
+ //! Return the number of chunks that were read too late.
+ size_t getNumberOfUnderruns() const;
+
+ //! Set underrun counter to 0.
+ void resetNumberOfUnderruns();
+
+private:
+ size_t framesize{0};
+ sample_t *nodata{nullptr};
+ size_t numberOfUnderruns{0};
+
+ AudioCacheIDManager idManager;
+ AudioCacheEventHandler eventHandler{idManager};
+};
diff --git a/src/audiocacheeventhandler.cc b/src/audiocacheeventhandler.cc
new file mode 100644
index 0000000..598cc15
--- /dev/null
+++ b/src/audiocacheeventhandler.cc
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocacheeventhandler.cc
+ *
+ * Sun Jan 3 19:57:55 CET 2016
+ * Copyright 2016 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 "audiocacheeventhandler.h"
+
+#include "audiocachefile.h"
+#include "audiocache.h"
+#include "audiocacheidmanager.h"
+
+enum class EventType {
+ LoadNext,
+ Close,
+};
+
+class CacheEvent {
+public:
+ EventType eventType;
+
+ // For close event:
+ cacheid_t id;
+
+ // For load next event:
+ size_t pos;
+ AudioCacheFile* afile;
+ CacheChannels channels;
+};
+
+AudioCacheEventHandler::AudioCacheEventHandler(AudioCacheIDManager& idManager)
+ // Hack to be able to forward declare CacheEvent:
+ : eventqueue(new std::list<CacheEvent>())
+ , idManager(idManager)
+{
+}
+
+AudioCacheEventHandler::~AudioCacheEventHandler()
+{
+ // Close all ids already enqueued to be closed.
+ clearEvents();
+
+ auto activeIDs = idManager.getActiveIDs();
+ for(auto id : activeIDs)
+ {
+ handleCloseCache(id);
+ }
+
+ // Hack to be able to forward declare CacheEvent:
+ delete eventqueue;
+}
+
+void AudioCacheEventHandler::start()
+{
+ if(running)
+ {
+ return;
+ }
+
+ running = true;
+ run();
+ sem_run.wait();
+}
+
+void AudioCacheEventHandler::stop()
+{
+ if(!running)
+ {
+ return;
+ }
+
+ running = false;
+ if(threaded)
+ {
+ sem.post();
+ wait_stop();
+ }
+}
+
+void AudioCacheEventHandler::setThreaded(bool threaded)
+{
+ if(this->threaded == threaded)
+ {
+ return;
+ }
+
+ if(threaded && !running)
+ {
+ start();
+ }
+
+ if(!threaded && running)
+ {
+ stop();
+ }
+
+ this->threaded = threaded;
+}
+
+bool AudioCacheEventHandler::getThreaded() const
+{
+ return threaded;
+}
+
+void AudioCacheEventHandler::lock()
+{
+ mutex.lock();
+}
+
+void AudioCacheEventHandler::unlock()
+{
+ mutex.unlock();
+}
+
+void AudioCacheEventHandler::pushLoadNextEvent(AudioCacheFile* afile,
+ size_t channel,
+ size_t pos, sample_t* buffer,
+ volatile bool* ready)
+{
+ CacheEvent e;
+ e.eventType = EventType::LoadNext;
+ e.pos = pos;
+ e.afile = afile;
+
+ CacheChannel c;
+ c.channel = channel;
+ c.samples = buffer;
+
+ *ready = false;
+ c.ready = ready;
+
+ e.channels.insert(e.channels.end(), c);
+
+ pushEvent(e);
+}
+
+void AudioCacheEventHandler::pushCloseEvent(cacheid_t id)
+{
+ CacheEvent e;
+ e.eventType = EventType::Close;
+ e.id = id;
+
+ pushEvent(e);
+}
+
+void AudioCacheEventHandler::setChunkSize(size_t chunksize)
+{
+ if(this->chunksize == chunksize)
+ {
+ return;
+ }
+
+ // Remove all events from event queue.
+ clearEvents();
+
+ // Skip all active cacheids and make their buffers point at nodata.
+ idManager.disableActive();
+
+ this->chunksize = chunksize;
+}
+
+size_t AudioCacheEventHandler::chunkSize()
+{
+ return chunksize;
+}
+
+AudioCacheFile& AudioCacheEventHandler::openFile(const std::string& filename)
+{
+ std::lock_guard<std::mutex> l(mutex);
+ return files.getFile(filename);
+}
+
+void AudioCacheEventHandler::clearEvents()
+{
+ std::lock_guard<std::mutex> l(mutex);
+
+ // Iterate all events ignoring load events and handling close events.
+ for(auto& event : *eventqueue)
+ {
+ if(event.eventType == EventType::Close)
+ {
+ handleCloseEvent(event);
+ }
+ }
+
+ eventqueue->clear();
+}
+
+void AudioCacheEventHandler::handleLoadNextEvent(CacheEvent& event)
+{
+ event.afile->readChunk(event.channels, event.pos, chunksize);
+}
+
+void AudioCacheEventHandler::handleCloseEvent(CacheEvent& e)
+{
+ handleCloseCache(e.id);
+}
+
+void AudioCacheEventHandler::handleCloseCache(cacheid_t cacheid)
+{
+ auto& cache = idManager.getCache(cacheid);
+ {
+ std::lock_guard<std::mutex> l(mutex);
+
+ files.releaseFile(cache.afile->getFilename());
+ }
+
+ delete[] cache.front;
+ delete[] cache.back;
+
+ idManager.releaseID(cacheid);
+}
+
+void AudioCacheEventHandler::handleEvent(CacheEvent& e)
+{
+ switch(e.eventType)
+ {
+ case EventType::LoadNext:
+ handleLoadNextEvent(e);
+ break;
+ case EventType::Close:
+ handleCloseEvent(e);
+ break;
+ }
+}
+
+void AudioCacheEventHandler::thread_main()
+{
+ sem_run.post(); // Signal that the thread has been started
+
+ while(running)
+ {
+ sem.wait();
+
+ mutex.lock();
+ if(eventqueue->empty())
+ {
+ mutex.unlock();
+ continue;
+ }
+
+ CacheEvent e = eventqueue->front();
+ eventqueue->pop_front();
+ mutex.unlock();
+
+ // TODO: Skip event if e.pos < cache.pos
+ //if(!e.active)
+ //{
+ // continue;
+ //}
+
+ handleEvent(e);
+ }
+}
+
+void AudioCacheEventHandler::pushEvent(CacheEvent& event)
+{
+ if(!threaded)
+ {
+ handleEvent(event);
+ return;
+ }
+
+ {
+ std::lock_guard<std::mutex> l(mutex);
+
+ bool found = false;
+
+ if(event.eventType == EventType::LoadNext)
+ {
+ for(auto& queuedEvent : *eventqueue)
+ {
+ if((queuedEvent.eventType == EventType::LoadNext) &&
+ (event.afile->getFilename() == queuedEvent.afile->getFilename()) &&
+ (event.pos == queuedEvent.pos))
+ {
+ // Append channel and buffer to the existing event.
+ queuedEvent.channels.insert(queuedEvent.channels.end(),
+ event.channels.begin(),
+ event.channels.end());
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if(!found)
+ {
+ // The event was not already on the list, create a new one.
+ eventqueue->push_back(event);
+ }
+ }
+
+ sem.post();
+}
diff --git a/src/audiocacheeventhandler.h b/src/audiocacheeventhandler.h
new file mode 100644
index 0000000..014cc00
--- /dev/null
+++ b/src/audiocacheeventhandler.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocacheeventhandler.h
+ *
+ * Sun Jan 3 19:57:55 CET 2016
+ * Copyright 2016 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.
+ */
+#pragma once
+
+#include <list>
+#include <vector>
+#include <mutex>
+
+#include "thread.h"
+#include "semaphore.h"
+
+#include "audiocachefile.h"
+#include "audiocacheidmanager.h"
+
+class CacheEvent;
+
+class AudioCacheEventHandler
+ : protected Thread
+{
+public:
+ AudioCacheEventHandler(AudioCacheIDManager& idManager);
+ ~AudioCacheEventHandler();
+
+ //! Start event handler thread.
+ //! This method blocks until the thread has actually been started.
+ void start();
+
+ //! Stop event handler thread.
+ //! This method blocks until the thread has actually been stopped.
+ void stop();
+
+ //! Set thread status and start/stop thread accordingly.
+ //! \param threaded Set to true to start thread or false to stop it.
+ void setThreaded(bool threaded);
+
+ //! Get current threaded status.
+ bool getThreaded() const;
+
+ //! Lock thread mutex.
+ //! This methods are supplied to make this class lockable by std::lock_guard
+ void lock();
+
+ //! Unlock thread mutex.
+ //! This methods are supplied to make this class lockable by std::lock_guard
+ void unlock();
+
+ void pushLoadNextEvent(AudioCacheFile* afile, size_t channel,
+ size_t pos, sample_t* buffer,
+ volatile bool* ready);
+ void pushCloseEvent(cacheid_t id);
+
+ void setChunkSize(size_t chunksize);
+ size_t chunkSize();
+
+ AudioCacheFile& openFile(const std::string& filename);
+
+protected:
+ AudioCacheFiles files;
+
+ std::mutex mutex;
+
+ void clearEvents();
+
+ void handleLoadNextEvent(CacheEvent& e);
+ void handleCloseEvent(CacheEvent& e);
+ void handleCloseCache(cacheid_t cacheid);
+
+ void handleEvent(CacheEvent& e);
+
+ // From Thread
+ void thread_main() override;
+
+ void pushEvent(CacheEvent& e);
+
+ std::list<CacheEvent>* eventqueue;
+
+ bool threaded{false};
+ Semaphore sem;
+ Semaphore sem_run;
+ bool running{false};
+
+ AudioCacheIDManager& idManager;
+
+ size_t chunksize{1024};
+};
diff --git a/src/cacheaudiofile.cc b/src/audiocachefile.cc
index 0d4aec9..c1fdd7b 100644
--- a/src/cacheaudiofile.cc
+++ b/src/audiocachefile.cc
@@ -24,17 +24,15 @@
* along with DrumGizmo; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
-#include "cacheaudiofile.h"
+#include "audiocachefile.h"
#include <assert.h>
#include <hugin.hpp>
-#include <audiotypes.h>
+#include "audiocache.h"
-#include "cachemanager.h"
-
-CacheAudioFile::CacheAudioFile(const std::string& filename)
+AudioCacheFile::AudioCacheFile(const std::string& filename)
: filename(filename)
{
fh = sf_open(filename.c_str(), SFM_READ, &sf_info);
@@ -43,9 +41,14 @@ CacheAudioFile::CacheAudioFile(const std::string& filename)
ERR(audiofile,"SNDFILE Error (%s): %s\n",
filename.c_str(), sf_strerror(fh));
}
+
+ if(sf_info.frames == 0)
+ {
+ printf("sf_info.frames == 0\n");
+ }
}
-CacheAudioFile::~CacheAudioFile()
+AudioCacheFile::~AudioCacheFile()
{
if(fh)
{
@@ -54,33 +57,33 @@ CacheAudioFile::~CacheAudioFile()
}
}
-size_t CacheAudioFile::getSize() const
+size_t AudioCacheFile::getSize() const
{
return sf_info.frames;
}
-const std::string& CacheAudioFile::getFilename() const
+const std::string& AudioCacheFile::getFilename() const
{
return filename;
}
-void CacheAudioFile::readChunk(const CacheChannels& channels,
+size_t AudioCacheFile::getChannelCount()
+{
+ return sf_info.channels;
+}
+
+void AudioCacheFile::readChunk(const CacheChannels& channels,
size_t pos, size_t num_samples)
{
- if(fh == nullptr)
- {
- printf("File handle is null.\n");
- return;
- }
+ assert(fh != nullptr); // File handle must never be nullptr
if(pos > sf_info.frames)
{
- printf("pos > sf_info.frames\n");
+ printf("pos (%d) > sf_info.frames (%d)\n", pos, (int)sf_info.frames);
return;
}
- sf_seek(fh, pos,
- SEEK_SET);
+ sf_seek(fh, pos, SEEK_SET);
size_t size = sf_info.frames - pos;
if(size > num_samples)
@@ -118,28 +121,35 @@ void CacheAudioFile::readChunk(const CacheChannels& channels,
}
}
-CacheAudioFile& CacheAudioFiles::getAudioFile(const std::string filename)
+AudioCacheFile& AudioCacheFiles::getFile(const std::string& filename)
{
+ AudioCacheFile* cacheAudioFile = nullptr;
+
auto it = audiofiles.find(filename);
if(it == audiofiles.end())
{
- it = audiofiles.insert(audiofiles.begin(), std::make_pair(filename, new CacheAudioFile(filename)));
- //it = audiofiles.find(filename);
+ cacheAudioFile = new AudioCacheFile(filename);
+ audiofiles.insert(std::make_pair(filename, cacheAudioFile));
+ }
+ else
+ {
+ cacheAudioFile = it->second;
}
- auto afile = it->second;
+ assert(cacheAudioFile);
// Increase ref count.
- ++afile->ref;
+ ++cacheAudioFile->ref;
- return *afile;
+ return *cacheAudioFile;
}
-void CacheAudioFiles::release(const std::string filename)
+void AudioCacheFiles::releaseFile(const std::string& filename)
{
auto it = audiofiles.find(filename);
if(it == audiofiles.end())
{
+ assert(false); // This should never happen!
return; // not open
}
diff --git a/src/audiocachefile.h b/src/audiocachefile.h
new file mode 100644
index 0000000..2ed1d47
--- /dev/null
+++ b/src/audiocachefile.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * cacheaudiofile.h
+ *
+ * Thu Dec 24 12:17:58 CET 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.
+ */
+#pragma once
+
+#include <string>
+#include <list>
+#include <map>
+#include <mutex>
+
+#include <sndfile.h>
+
+#include <audiotypes.h>
+
+class CacheChannel {
+public:
+ size_t channel;
+ sample_t* samples;
+ size_t num_samples;
+ volatile bool* ready;
+};
+
+using CacheChannels = std::list<CacheChannel>;
+
+//! This class is used to encapsulate reading from a single file source.
+//! The access is ref counted so that the file is only opened once and closed
+//! when it is no longer required.
+class AudioCacheFile {
+ friend class AudioCacheFiles;
+ friend class TestableAudioCacheFiles;
+public:
+ //! Create file handle for filename.
+ AudioCacheFile(const std::string& filename);
+
+ //! Closes file handle.
+ ~AudioCacheFile();
+
+ //! Get sample count of the file connected with this cache object.
+ size_t getSize() const;
+
+ //! Get filename of the file connected with this cache object.
+ const std::string& getFilename() const;
+
+ //! Get number of channels in the file
+ size_t getChannelCount();
+
+ //! Read audio data from the file into the supplied channel caches.
+ void readChunk(const CacheChannels& channels, size_t pos, size_t num_samples);
+
+private:
+ int ref{0};
+ SNDFILE* fh{nullptr};
+ SF_INFO sf_info;
+ std::string filename;
+};
+
+class AudioCacheFiles {
+public:
+ //! Get the CacheAudioFile object corresponding to filename.
+ //! If it does not exist it will be created.
+ //! It's ref count will be increased.
+ AudioCacheFile& getFile(const std::string& filename);
+
+ //! Release the CacheAudioFile corresponding to filename.
+ //! It's ref count will be decreased.
+ //! If the ref count reaches 0 the object will be deleted.
+ void releaseFile(const std::string& filename);
+
+protected:
+ std::map<std::string, AudioCacheFile*> audiofiles;
+ std::mutex mutex;
+};
diff --git a/src/audiocacheidmanager.cc b/src/audiocacheidmanager.cc
new file mode 100644
index 0000000..5d4248b
--- /dev/null
+++ b/src/audiocacheidmanager.cc
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocacheidmanager.cc
+ *
+ * Tue Jan 5 10:59:37 CET 2016
+ * Copyright 2016 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 "audiocacheidmanager.h"
+
+#include <limits>
+#include <assert.h>
+
+AudioCacheIDManager::AudioCacheIDManager()
+{
+}
+
+AudioCacheIDManager::~AudioCacheIDManager()
+{
+ assert(availableids.size() == id2cache.size()); // All ids should be released.
+}
+
+void AudioCacheIDManager::init(unsigned int capacity)
+{
+ std::lock_guard<std::mutex> guard(mutex);
+
+ id2cache.resize(capacity);
+ availableids.resize(capacity);
+ for(size_t i = 0; i < capacity; ++i)
+ {
+ availableids[i] = i;
+ }
+}
+
+cache_t& AudioCacheIDManager::getCache(cacheid_t id)
+{
+ std::lock_guard<std::mutex> guard(mutex);
+
+ assert(id != CACHE_NOID);
+ assert(id != CACHE_DUMMYID);
+ assert(id >= 0);
+ assert(id < id2cache.size());
+ assert(id2cache[id].id == id);
+
+ return id2cache[id];
+}
+
+cacheid_t AudioCacheIDManager::registerID(const cache_t& cache)
+{
+ std::lock_guard<std::mutex> guard(mutex);
+
+ cacheid_t id = CACHE_NOID;
+
+ if(availableids.empty())
+ {
+ return CACHE_DUMMYID;
+ }
+ else
+ {
+ id = availableids.back();
+ availableids.pop_back();
+ }
+
+ assert(id2cache[id].id == CACHE_NOID); // Make sure it is not already in use
+
+ id2cache[id] = cache;
+ id2cache[id].id = id;
+
+ return id;
+}
+
+void AudioCacheIDManager::releaseID(cacheid_t id)
+{
+ std::lock_guard<std::mutex> guard(mutex);
+
+ assert(id2cache[id].id != CACHE_NOID); // Test if it wasn't already released.
+
+ id2cache[id].id = CACHE_NOID;
+
+ availableids.push_back(id);
+}
+
+void AudioCacheIDManager::disableActive()
+{
+ // Run through all active cache_ts and disable them.
+ for(auto& cache : id2cache)
+ {
+ if(cache.id != CACHE_NOID)
+ {
+ // Force use of nodata in all of the rest of the next() calls:
+ cache.localpos = std::numeric_limits<size_t>::max();
+ cache.ready = false;
+ }
+ }
+}
+
+std::vector<cacheid_t> AudioCacheIDManager::getActiveIDs()
+{
+ std::vector<cacheid_t> activeIDs;
+
+ for(auto& cache : id2cache)
+ {
+ if(cache.id != CACHE_NOID)
+ {
+ activeIDs.push_back(cache.id);
+ }
+ }
+
+ return activeIDs;
+}
diff --git a/src/audiocacheidmanager.h b/src/audiocacheidmanager.h
new file mode 100644
index 0000000..7f203c9
--- /dev/null
+++ b/src/audiocacheidmanager.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocacheidmanager.h
+ *
+ * Tue Jan 5 10:59:37 CET 2016
+ * Copyright 2016 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.
+ */
+#pragma once
+
+#include <stdlib.h>
+
+#include <vector>
+
+#include <audiotypes.h>
+
+#include <mutex>
+
+class AudioCacheFile;
+
+#define CACHE_DUMMYID -2
+#define CACHE_NOID -1
+
+typedef int cacheid_t;
+
+typedef struct {
+ cacheid_t id{CACHE_NOID}; //< Current id of this cache_t. CACHE_NOID means not in use.
+
+ AudioCacheFile* afile{nullptr};
+ size_t channel{0};
+ size_t pos{0}; //< File position
+ volatile bool ready{false};
+ sample_t* front{nullptr};
+ sample_t* back{nullptr};
+ size_t localpos{0}; //< Intra buffer (front) position.
+
+ sample_t* preloaded_samples{nullptr}; // nullptr means preload buffer not active.
+ size_t preloaded_samples_size{0};
+} cache_t;
+
+class AudioCacheIDManager {
+ friend class AudioCacheEventHandler;
+public:
+ AudioCacheIDManager();
+ ~AudioCacheIDManager();
+
+ void init(unsigned int capacity);
+
+ // #thread safe
+ // #hard real-time safe
+ cache_t& getCache(cacheid_t id);
+
+ // Thread safe
+ // Real-time safe
+ cacheid_t registerID(const cache_t& cache);
+
+ // Thread safe
+ // Real-time safe
+ void releaseID(cacheid_t id);
+
+protected:
+ // For AudioCacheEventHandler
+ void disableActive();
+ std::vector<cacheid_t> getActiveIDs();
+
+ std::mutex mutex;
+
+ std::vector<cache_t> id2cache;
+ std::vector<cacheid_t> availableids;
+};
diff --git a/src/cachemanager.cc b/src/cachemanager.cc
deleted file mode 100644
index d394d8c..0000000
--- a/src/cachemanager.cc
+++ /dev/null
@@ -1,438 +0,0 @@
-/* -*- 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 <hugin.hpp>
-
-#include "cacheaudiofile.h"
-
-#define CHUNKSIZE(x) (x * CHUNK_MULTIPLIER)
-
-
-CacheManager::CacheManager()
- : framesize(0)
- , nodata(nullptr)
-{
-}
-
-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
-sample_t *CacheManager::open(AudioFile *file, size_t initial_samples_needed,
- int channel, cacheid_t &id)
-{
- if(!file->isValid())
- {
- // File preload not yet ready - skip this sample.
- id = CACHE_DUMMYID;
- assert(nodata);
- return nodata;
- }
-
- {
- 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;
- }
-
- CacheAudioFile& afile = files.getAudioFile(file->filename);
-
- cache_t c;
- c.afile = &afile;
- c.channel = channel;
-
- // next call to 'next' will read from this point.
- c.localpos = initial_samples_needed;
-
- c.front = nullptr; // This is allocated when needed.
- c.back = nullptr; // This is allocated when needed.
-
- // cropped_size is the preload chunk size cropped to sample length.
- size_t cropped_size = file->preloadedsize - c.localpos;
- cropped_size /= framesize;
- cropped_size *= framesize;
- cropped_size += initial_samples_needed;
-
- if(file->preloadedsize == file->size)
- {
- // We have preloaded the entire file, so use it.
- cropped_size = file->preloadedsize;
- }
-
- c.preloaded_samples = file->data;
- c.preloaded_samples_size = cropped_size;
-
- // next read from disk will read from this point.
- c.pos = cropped_size;//c.preloaded_samples_size;
-
- {
- MutexAutolock l(m_ids);
- id2cache[id] = c;
- }
- // NOTE: Below this point we can no longer write to 'c'.
-
- // Only load next buffer if there are more data in the file to be loaded...
- if(c.pos < file->size)
- {
- cache_t& c = id2cache[id]; // Create new writeable 'c'.
-
- if(c.back == nullptr)
- {
- c.back = new sample_t[CHUNKSIZE(framesize)];
- }
-
- cevent_t e =
- createLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready);
- pushEvent(e);
- }
-
- return c.preloaded_samples; // return 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.preloaded_samples)
- {
-
- // We are playing from memory:
- if(c.localpos < c.preloaded_samples_size)
- {
- sample_t *s = c.preloaded_samples + c.localpos;
- c.localpos += framesize;
- return s;
- }
-
- c.preloaded_samples = nullptr; // Start using samples from disk.
-
- }
- else
- {
-
- // We are playing from cache:
- if(c.localpos < CHUNKSIZE(framesize))
- {
- sample_t *s = c.front + c.localpos;
- c.localpos += framesize;
- return s;
- }
- }
-
- if(!c.ready)
- {
- //printf("#%d: NOT READY!\n", id); // TODO: Count and show in UI?
- return nodata;
- }
-
- // Swap buffers
- std::swap(c.front, c.back);
-
- // Next time we go here we have already read the first frame.
- c.localpos = framesize;
-
- c.pos += CHUNKSIZE(framesize);
-
- if(c.pos < c.afile->getSize())
- {
- if(c.back == nullptr)
- {
- c.back = new sample_t[CHUNKSIZE(framesize)];
- }
-
- cevent_t e = createLoadNextEvent(c.afile, c.channel, c.pos, c.back, &c.ready);
- pushEvent(e);
- }
-
- if(!c.front)
- {
- printf("We shouldn't get here... ever!\n");
- assert(false);
- return nodata;
- }
-
- 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)
-{
- MutexAutolock le(m_events);
- MutexAutolock li(m_ids);
-
- if(framesize > this->framesize)
- {
- delete[] nodata;
- nodata = new sample_t[framesize];
-
- for(size_t i = 0; i < framesize; ++i)
- {
- nodata[i] = 0.0f;
- }
- }
-
- // Run through all active cache_ts and disable them.
- for(auto it = id2cache.begin(); it != id2cache.end(); ++it)
- {
- cacheid_t cacheid = it - id2cache.begin();
-
- // Look up id in available ids:
- bool found = false;
- for(auto i = availableids.begin(); i != availableids.end(); ++i)
- {
- if(cacheid == *i)
- {
- found = true;
- break;
- }
- }
-
- if(!found)
- {
- // Force use of nodata in all of the rest of the next() calls.
- it->localpos = CHUNKSIZE(framesize);
- it->ready = false;
- }
- }
-
- this->framesize = framesize;
-}
-
-void CacheManager::setAsyncMode(bool async)
-{
- // TODO: Clean out read queue.
- // TODO: Block until reader thread is idle, otherwise we might screw up the
- // buffers...?
- threaded = async;
-}
-
-void CacheManager::handleLoadNextEvent(cevent_t &event)
-{
- event.afile->readChunk(event.channels, event.pos, CHUNKSIZE(framesize));
-}
-
-void CacheManager::handleCloseEvent(cevent_t &e)
-{
- handleCloseCache(e.id);
-}
-
-void CacheManager::handleCloseCache(cacheid_t& cacheid)
-{
- cache_t& cache = id2cache[cacheid];
- {
- MutexAutolock l(m_events);
-
- files.release(cache.afile->getFilename());
- }
-
- delete[] cache.front;
- delete[] cache.back;
-
- {
- MutexAutolock l(m_ids);
- availableids.push_back(cacheid);
- }
-}
-
-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;
- }
-
- {
- MutexAutolock l(m_events);
-
- bool found = false;
-
- if(e.cmd == LOADNEXT)
- {
- for(auto it = eventqueue.begin(); it != eventqueue.end(); ++it)
- {
- auto& event = *it;
- if((event.cmd == LOADNEXT) &&
- (e.afile->getFilename() == event.afile->getFilename()) &&
- (e.pos == event.pos))
- {
- // Append channel and buffer to the existing event.
- event.channels.insert(event.channels.end(), e.channels.begin(),
- e.channels.end());
- found = true;
- break;
- }
- }
- }
-
- if(!found)
- {
- // The event was not already on the list, create a new one.
- eventqueue.push_back(e);
- }
- }
-
- sem.post();
-}
-
-CacheManager::cevent_t
-CacheManager::createLoadNextEvent(CacheAudioFile *afile, size_t channel,
- size_t pos, sample_t* buffer,
- volatile bool* ready)
-{
- cevent_t e;
- e.cmd = LOADNEXT;
- e.pos = pos;
- e.afile = afile;
-
- CacheChannel c;
- c.channel = channel;
- c.samples = buffer;
-
- *ready = false;
- c.ready = ready;
-
- e.channels.insert(e.channels.end(), c);
-
- 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
deleted file mode 100644
index a43d19a..0000000
--- a/src/cachemanager.h
+++ /dev/null
@@ -1,221 +0,0 @@
-/* -*- 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.
- */
-#pragma once
-
-#include <string>
-#include <list>
-#include <vector>
-
-#include "thread.h"
-#include "semaphore.h"
-#include "mutex.h"
-
-#include "audiotypes.h"
-#include "audiofile.h"
-#include "cacheaudiofile.h"
-
-#define CACHE_DUMMYID -2
-#define CACHE_NOID -1
-
-#define CHUNK_MULTIPLIER 16
-
-class AudioFile;
-class CacheAudioFile;
-class CacheAudioFiles;
-
-typedef int cacheid_t;
-
-class CacheChannel {
-public:
- size_t channel;
- sample_t* samples;
- size_t num_samples;
- volatile bool* ready;
-};
-
-class CacheChannels : public std::list<CacheChannel> {};
-
-//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);
-
- /**
- * Set internal framesize used when iterating through cache buffers.
- */
- void setFrameSize(size_t framesize);
-
- /**
- * Control reader thread.
- * Set to true to make reading happen threaded, false to do all reading sync.
- */
- void setAsyncMode(bool async);
-
-private:
- ///! Internal thread main method
- void thread_main();
-
- size_t framesize;
- sample_t *nodata;
-
- typedef struct {
- CacheAudioFile* afile;
- size_t channel;
- size_t pos; //< File possition
- volatile bool ready;
- sample_t *front;
- sample_t *back;
- size_t localpos; //< Intra buffer (front) position.
-
- sample_t* preloaded_samples; // NULL means not active.
- size_t preloaded_samples_size;
-
- } 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;
- CacheAudioFile* afile;
- CacheChannels channels;
- } cevent_t;
-
- cevent_t createLoadNextEvent(CacheAudioFile* afile, size_t channel,
- size_t pos, sample_t* buffer,
- volatile bool* ready);
- cevent_t createCloseEvent(cacheid_t id);
-
- void handleLoadNextEvent(cevent_t& e);
- void handleCloseEvent(cevent_t& e);
- void handleCloseCache(cacheid_t& cacheid);
-
- 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;
-
- CacheAudioFiles files;
- //std::map<std::string, CacheAudioFile*> files;
-};
diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc
index 5348324..00e93d1 100644
--- a/src/drumgizmo.cc
+++ b/src/drumgizmo.cc
@@ -54,12 +54,12 @@ DrumGizmo::DrumGizmo(AudioOutputEngine *o, AudioInputEngine *i)
, freewheel(false)
{
is_stopping = false;
- cacheManager.init(1000, true); // start thread
+ audioCache.init(1000); // start thread
}
DrumGizmo::~DrumGizmo()
{
- cacheManager.deinit(); // stop thread
+ audioCache.deinit(); // stop thread
}
bool DrumGizmo::loadkit(std::string file)
@@ -185,8 +185,8 @@ void DrumGizmo::setFrameSize(size_t 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);
+ audioCache.setFrameSize(framesize);
+ printf("audioCache.setFrameSize\n"); fflush(stdout);
}
}
@@ -196,7 +196,7 @@ void DrumGizmo::setFreeWheel(bool freewheel)
// than realtime.
if(freewheel != this->freewheel) {
this->freewheel = freewheel;
- cacheManager.setAsyncMode(!freewheel);
+ audioCache.setAsyncMode(!freewheel);
}
}
@@ -424,7 +424,7 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)
if(evt->cache_id == CACHE_NOID) {
size_t initial_chunksize = (pos + sz) - evt->offset;
- evt->buffer = cacheManager.open(af, initial_chunksize,
+ evt->buffer = audioCache.open(af, initial_chunksize,
af->filechannel, evt->cache_id);
evt->buffer_size = initial_chunksize;
}
@@ -484,13 +484,13 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz)
evt->t += evt->buffer_size; // Add internal buffer counter to "global" event counter.
if((evt->t < af->size) && (evt->rampdown != 0)) {
- evt->buffer = cacheManager.next(evt->cache_id, evt->buffer_size);
+ evt->buffer = audioCache.next(evt->cache_id, evt->buffer_size);
} else {
removeevent = true;
}
if(removeevent) {
- cacheManager.close(evt->cache_id);
+ audioCache.close(evt->cache_id);
}
}
}
diff --git a/src/drumgizmo.h b/src/drumgizmo.h
index 041ca35..1e38e08 100644
--- a/src/drumgizmo.h
+++ b/src/drumgizmo.h
@@ -37,7 +37,7 @@
#include "drumkit.h"
#include "drumkitloader.h"
-#include "cachemanager.h"
+#include "audiocache.h"
#include "mutex.h"
@@ -96,7 +96,7 @@ private:
std::map<std::string, AudioFile *> audiofiles;
- CacheManager cacheManager;
+ AudioCache audioCache;
DrumKit kit;
size_t framesize;
diff --git a/src/drumkitloader.cc b/src/drumkitloader.cc
index 413d3f4..203db04 100644
--- a/src/drumkitloader.cc
+++ b/src/drumkitloader.cc
@@ -30,7 +30,6 @@
#include "drumkitparser.h"
#include "drumgizmo.h"
-#include "cachemanager.h"
DrumKitLoader::DrumKitLoader()
: semaphore("drumkitloader")
diff --git a/src/events.h b/src/events.h
index ea897f1..26eaf3f 100644
--- a/src/events.h
+++ b/src/events.h
@@ -35,7 +35,7 @@
#include "audiofile.h"
#include "audio.h"
#include "mutex.h"
-#include "cachemanager.h"
+#include "audiocache.h"
typedef unsigned int timepos_t;
diff --git a/test/Makefile.am b/test/Makefile.am
index 6526f22..1f605e5 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,23 +1,66 @@
# Rules for the test code (use `make check` to execute)
include $(top_srcdir)/src/Makefile.am.drumgizmo
-TESTS = engine gui resampler lv2 configfile cachemanager
+TESTS = engine gui resampler lv2 configfile audiocache audiocachefile \
+ audiocacheidmanager audiocacheeventhandler
check_PROGRAMS = $(TESTS)
-cachemanager_CXXFLAGS = -DOUTPUT=\"cachemanager\" $(CPPUNIT_CFLAGS) \
+audiocache_CXXFLAGS = -DOUTPUT=\"audiocache\" $(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 \
+audiocache_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS)
+audiocache_SOURCES = \
+ $(top_srcdir)/src/audiocache.cc \
+ $(top_srcdir)/src/audiocacheeventhandler.cc \
+ $(top_srcdir)/src/audiocachefile.cc \
+ $(top_srcdir)/src/audiocacheidmanager.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
+ audiocachetest.cc
+
+audiocachefile_CXXFLAGS = -DOUTPUT=\"audiocachefile\" $(CPPUNIT_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/include \
+ -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS)
+audiocachefile_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS)
+audiocachefile_SOURCES = \
+ $(top_srcdir)/src/audiocachefile.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 \
+ audiocachefiletest.cc
+
+audiocacheidmanager_CXXFLAGS = -DOUTPUT=\"audiocacheidmanager\" \
+ $(CPPUNIT_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/include \
+ -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(SNDFILE_CFLAGS)
+audiocacheidmanager_LDFLAGS = $(CPPUNIT_LIBS) $(SNDFILE_LIBS)
+audiocacheidmanager_SOURCES = \
+ $(top_srcdir)/src/audiocacheidmanager.cc \
+ test.cc \
+ audiocacheidmanagertest.cc
+
+audiocacheeventhandler_CXXFLAGS = -DOUTPUT=\"audiocacheeventhandler\" \
+ $(CPPUNIT_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_srcdir)/include \
+ -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS)
+audiocacheeventhandler_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS)
+audiocacheeventhandler_SOURCES = \
+ $(top_srcdir)/src/audiocacheeventhandler.cc \
+ $(top_srcdir)/src/audiocacheidmanager.cc \
+ $(top_srcdir)/src/audiocachefile.cc \
+ $(top_srcdir)/src/mutex.cc \
+ $(top_srcdir)/src/thread.cc \
+ $(top_srcdir)/src/semaphore.cc \
+ test.cc \
+ audiocacheeventhandlertest.cc
engine_CXXFLAGS = -DOUTPUT=\"engine\" $(CPPUNIT_CFLAGS) \
-I$(top_srcdir)/src -I$(top_srcdir)/include \
diff --git a/src/cacheaudiofile.h b/test/audiocacheeventhandlertest.cc
index 4f78247..a8c3777 100644
--- a/src/cacheaudiofile.h
+++ b/test/audiocacheeventhandlertest.cc
@@ -1,9 +1,9 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
- * cacheaudiofile.h
+ * audiocacheeventhandlertest.cc
*
- * Thu Dec 24 12:17:58 CET 2015
- * Copyright 2015 Bent Bisballe Nyeng
+ * Thu Jan 7 15:44:14 CET 2016
+ * Copyright 2016 Bent Bisballe Nyeng
* deva@aasimon.org
****************************************************************************/
@@ -24,40 +24,35 @@
* along with DrumGizmo; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
-#pragma once
+#include <cppunit/extensions/HelperMacros.h>
-#include <string>
-#include <list>
-#include <map>
+#include <audiocacheeventhandler.h>
-#include <sndfile.h>
+class TestableAudioCacheEventHandler
+ : public AudioCacheEventHandler
+{
+public:
+
+};
-class CacheChannels;
+class AudioCacheEventHandlerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(AudioCacheEventHandlerTest);
+ CPPUNIT_TEST(threadedTest);
+ CPPUNIT_TEST_SUITE_END();
-class CacheAudioFile {
- friend class CacheAudioFiles;
public:
- CacheAudioFile(const std::string& filename);
- ~CacheAudioFile();
-
- size_t getSize() const;
- const std::string& getFilename() const;
+ void setUp() {}
+ void tearDown() {}
- void readChunk(const CacheChannels& channels,
- size_t pos, size_t num_samples);
+ void threadedTest()
+ {
+ AudioCacheIDManager idManager;
+ idManager.init(10);
-private:
- int ref{0};
- SNDFILE* fh{nullptr};
- SF_INFO sf_info;
- std::string filename;
+ AudioCacheEventHandler eventHandler(idManager);
+ }
};
-class CacheAudioFiles {
-public:
- CacheAudioFile& getAudioFile(const std::string filename);
- void release(const std::string filename);
-
-private:
- std::map<std::string, CacheAudioFile*> audiofiles;
-};
+// Registers the fixture into the 'registry'
+CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheEventHandlerTest);
diff --git a/test/audiocachefiletest.cc b/test/audiocachefiletest.cc
new file mode 100644
index 0000000..a70091c
--- /dev/null
+++ b/test/audiocachefiletest.cc
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocachefiletest.cc
+ *
+ * Thu Jan 7 15:43:12 CET 2016
+ * Copyright 2016 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 <cstring>
+
+#include <audiocachefile.h>
+#include <audiofile.h>
+
+class TestableAudioCacheFiles : public AudioCacheFiles
+{
+public:
+ //CacheAudioFile& getAudioFile(const std::string& filename);
+ //void release(const std::string& filename);
+ int getRef(const std::string& filename)
+ {
+ if(audiofiles.find(filename) == audiofiles.end())
+ {
+ return -1;
+ }
+
+ return audiofiles[filename]->ref;
+ }
+};
+
+class AudioCacheFileTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(AudioCacheFileTest);
+ CPPUNIT_TEST(refTest);
+ CPPUNIT_TEST(readTest);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp() {}
+ void tearDown() {}
+
+ void refTest()
+ {
+ TestableAudioCacheFiles audiofiles;
+ std::string filename = "kit/ride-single-channel.wav";
+ CPPUNIT_ASSERT_EQUAL(-1, audiofiles.getRef(filename));
+
+ audiofiles.getFile(filename);
+ CPPUNIT_ASSERT_EQUAL(1, audiofiles.getRef(filename));
+
+ audiofiles.getFile(filename);
+ CPPUNIT_ASSERT_EQUAL(2, audiofiles.getRef(filename));
+
+ audiofiles.releaseFile(filename);
+ CPPUNIT_ASSERT_EQUAL(1, audiofiles.getRef(filename));
+
+ audiofiles.releaseFile(filename);
+ CPPUNIT_ASSERT_EQUAL(-1, audiofiles.getRef(filename));
+ }
+
+ void readTestHelper(size_t bufferSize)
+ {
+ printf("Test buffer size: %d samples\n", bufferSize);
+
+ std::string filename = "kit/ride-multi-channel.wav";
+ AudioFile* refFile[13];
+ for(size_t c = 0; c < 13; ++c)
+ {
+ refFile[c] = new AudioFile(filename, c);
+ refFile[c]->load();
+ }
+
+ AudioCacheFile file(filename);
+ CPPUNIT_ASSERT_EQUAL(filename, file.getFilename());
+ CPPUNIT_ASSERT_EQUAL(13, (int)file.getChannelCount()); // Sanity check
+
+ CacheChannels channels;
+
+ sample_t samples[13][bufferSize];
+ volatile bool ready[13];
+ for(size_t c = 0; c < 13; ++c)
+ {
+ for(int c = 0; c < 13; ++c)
+ {
+ for(int i = 0; i < bufferSize; ++i)
+ {
+ samples[c][i] = 42;
+ }
+ }
+
+ channels.push_back(
+ {
+ c, // channel
+ samples[c], // samples
+ bufferSize, // max_num_samples
+ &ready[c] // ready
+ }
+ );
+ }
+
+ for(size_t offset = 0; offset < file.getSize(); offset += bufferSize)
+ {
+ for(size_t c = 0; c < 13; ++c)
+ {
+ ready[c] = false;
+ }
+
+ size_t readSize = file.getSize() - offset;
+ if(readSize > bufferSize)
+ {
+ readSize = bufferSize;
+ }
+ else
+ {
+ printf("Last read: %d samples\n", readSize);
+ }
+
+ file.readChunk(channels, offset, readSize);
+
+ for(size_t c = 0; c < 13; ++c)
+ {
+ CPPUNIT_ASSERT_EQUAL(true, ready[c]?true:false);
+ }
+
+ sample_t diff[13] = {0.0};
+ for(int c = 0; c < 13; ++c)
+ {
+ for(int i = 0; i < readSize; ++i)
+ {
+ diff[c] += abs(refFile[c]->data[i + offset] - samples[c][i]);
+ }
+ }
+
+ for(int c = 0; c < 13; ++c)
+ {
+ CPPUNIT_ASSERT_EQUAL((sample_t)0.0, diff[c]);
+ }
+ }
+
+ for(size_t c = 0; c < 13; ++c)
+ {
+ delete refFile[c];
+ }
+ }
+
+ void readTest()
+ {
+ // Exhaustive test for 1...64
+ for(size_t bufferSize = 1; bufferSize < 64; ++bufferSize)
+ {
+ readTestHelper(bufferSize);
+ }
+
+ // Binary test for 64 .. 4096
+ for(size_t bufferSize = 64; bufferSize < 4096; bufferSize *= 2)
+ {
+ readTestHelper(bufferSize);
+ }
+
+ // And some sporadic tests for some "wierd" sizes.
+ for(size_t bufferSize = 65; bufferSize < 4096; bufferSize *= 1.1)
+ {
+ readTestHelper(bufferSize);
+ }
+ }
+
+};
+
+// Registers the fixture into the 'registry'
+CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheFileTest);
diff --git a/test/audiocacheidmanagertest.cc b/test/audiocacheidmanagertest.cc
new file mode 100644
index 0000000..58bff4e
--- /dev/null
+++ b/test/audiocacheidmanagertest.cc
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/***************************************************************************
+ * audiocacheidmanagertest.cc
+ *
+ * Thu Jan 7 15:42:31 CET 2016
+ * Copyright 2016 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 <audiocacheidmanager.h>
+
+class TestableAudioCacheIDManager : public AudioCacheIDManager {
+public:
+ int getAvailableIds()
+ {
+ return availableids.size();
+ }
+};
+
+class AudioCacheIDManagerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(AudioCacheIDManagerTest);
+ CPPUNIT_TEST(registerReleaseTest);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp() {}
+ void tearDown() {}
+
+ void registerReleaseTest()
+ {
+ TestableAudioCacheIDManager manager;
+ manager.init(2);
+
+ cache_t c1; c1.afile = (AudioCacheFile*)1;
+ auto id1 = manager.registerId(c1);
+ CPPUNIT_ASSERT(id1 != CACHE_DUMMYID);
+ CPPUNIT_ASSERT(id1 != CACHE_NOID);
+ CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIds());
+
+ cache_t c2; c2.afile = (AudioCacheFile*)2;
+ auto id2 = manager.registerId(c2);
+ CPPUNIT_ASSERT(id2 != CACHE_DUMMYID);
+ CPPUNIT_ASSERT(id2 != CACHE_NOID);
+ CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIds());
+
+ cache_t c3; c3.afile = (AudioCacheFile*)3;
+ auto id3 = manager.registerId(c3);
+ CPPUNIT_ASSERT(id3 == CACHE_DUMMYID);
+ CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIds());
+
+ cache_t& tc1 = manager.getCache(id1);
+ CPPUNIT_ASSERT_EQUAL(c1.afile, tc1.afile);
+
+ cache_t& tc2 = manager.getCache(id2);
+ CPPUNIT_ASSERT_EQUAL(c2.afile, tc2.afile);
+
+ manager.releaseId(id1);
+ CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIds());
+
+ cache_t c4; c4.afile = (AudioCacheFile*)4;
+ auto id4 = manager.registerId(c4);
+ CPPUNIT_ASSERT(id4 != CACHE_DUMMYID);
+ CPPUNIT_ASSERT(id4 != CACHE_NOID);
+ CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIds());
+
+ cache_t& tc4 = manager.getCache(id4);
+ CPPUNIT_ASSERT_EQUAL(c4.afile, tc4.afile);
+
+ manager.releaseId(id2);
+ CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIds());
+
+ manager.releaseId(id4);
+ CPPUNIT_ASSERT_EQUAL(2, manager.getAvailableIds());
+ }
+};
+
+// Registers the fixture into the 'registry'
+CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheIDManagerTest);
diff --git a/test/audiocachetest.cc b/test/audiocachetest.cc
new file mode 100644
index 0000000..0296490
--- /dev/null
+++ b/test/audiocachetest.cc
@@ -0,0 +1,177 @@
+/* -*- 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 <audiofile.h>
+#include <audiocache.h>
+#include <unistd.h>
+
+#define FRAMESIZE 64//1024
+
+class AudioCacheTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(AudioCacheTest);
+ CPPUNIT_TEST(singleChannelNonThreaded);
+ CPPUNIT_TEST(singleChannelThreaded);
+ CPPUNIT_TEST(multiChannelNonThreaded);
+ CPPUNIT_TEST(multiChannelThreaded);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void setUp() {}
+ void tearDown() {}
+
+ //! Test runner.
+ //! \param filename The name of the file to read.
+ //! \param channel The channel number to do comparison on.
+ //! \param thread Control if this test is running in threaded mode or not.
+ //! \param framesize The initial framesize to use.
+ void testHelper(const char* filename, int channel, bool threaded,
+ int framesize)
+ {
+ // Reference file:
+ AudioFile audioFileRef(filename, channel);
+ printf("audioFileRef.load\n");
+ audioFileRef.load(ALL_SAMPLES);
+
+ // Input file:
+ AudioFile audioFile(filename, channel);
+ printf("audioFile.load\n");
+ audioFile.load(4096);
+
+ AudioCache audioCache;
+ printf("audioCache.init\n");
+ audioCache.init(100);
+ audioCache.setAsyncMode(threaded);
+
+ // Set initial (upper limit) framesize
+ audioCache.setFrameSize(framesize);
+
+ cacheid_t id;
+
+ for(size_t initial_samples_needed = 0;
+ initial_samples_needed < (framesize - 1); ++initial_samples_needed)
+ {
+
+ printf("open: initial_samples_needed: %d\n", initial_samples_needed);
+ sample_t *samples =
+ audioCache.open(&audioFile, 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(audioFileRef.data[offset], samples[i]);
+ ++offset;
+ }
+
+ // Test the rest
+ while(offset < audioFileRef.size)
+ {
+ if(threaded)
+ {
+ // Wait until we are finished reading
+ int timeout = 1000;
+ while(!audioCache.isReady(id))
+ {
+ usleep(1000);
+ if(--timeout == 0)
+ {
+ CPPUNIT_ASSERT(false); // timeout
+ }
+ }
+ }
+
+ samples = audioCache.next(id, size);
+
+ CPPUNIT_ASSERT_EQUAL(0, (int)audioCache.getNumberOfUnderruns());
+
+ for(size_t i = 0; (i < size) && (offset < audioFileRef.size); ++i)
+ {
+ if(audioFileRef.data[offset] != samples[i])
+ {
+ printf("-----> offset: %d, size: %d, diff: %d,"
+ " i: %d, size: %d, block-diff: %d\n",
+ offset, audioFileRef.size, audioFileRef.size - offset,
+ i, size, size - i);
+ }
+ CPPUNIT_ASSERT_EQUAL(audioFileRef.data[offset], samples[i]);
+ ++offset;
+ }
+ }
+
+ audioCache.close(id);
+ }
+
+ printf("done\n");
+ }
+
+ void singleChannelNonThreaded()
+ {
+ printf("\nsinglechannel_nonthreaded()\n");
+ const char filename[] = "kit/ride-single-channel.wav";
+ int channel = 0;
+ bool threaded = false;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ }
+
+ void singleChannelThreaded()
+ {
+ printf("\nsinglechannel_threaded()\n");
+ const char filename[] = "kit/ride-single-channel.wav";
+ int channel = 0;
+ bool threaded = true;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ }
+
+ void multiChannelNonThreaded()
+ {
+ printf("\nmultichannel_nonthreaded()\n");
+ const char filename[] = "kit/ride-multi-channel.wav";
+ int channel = 0;
+ bool threaded = false;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ ++channel;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ }
+
+ void multiChannelThreaded()
+ {
+ printf("\nmultichannel_threaded()\n");
+ const char filename[] = "kit/ride-multi-channel.wav";
+ int channel = 0;
+ bool threaded = true;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ ++channel;
+ testHelper(filename, channel, threaded, FRAMESIZE);
+ }
+
+};
+
+// Registers the fixture into the 'registry'
+CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheTest);
diff --git a/test/cachemanagertest.cc b/test/cachemanagertest.cc
deleted file mode 100644
index d521f83..0000000
--- a/test/cachemanagertest.cc
+++ /dev/null
@@ -1,149 +0,0 @@
-/* -*- 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>
-
-#define FRAMESIZE 1024
-
-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(4096);
-
- CacheManager cm;
- printf("cm.init\n");
- cm.init(100, threaded);
-
- cm.setFrameSize(FRAMESIZE);
-
- cacheid_t id;
- // TODO: test 0 ... FRAMESIZE - 1
- size_t initial_samples_needed = (FRAMESIZE - 1) / 2;
-
- printf("open: initial_samples_needed: %d\n", initial_samples_needed);
- 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++) {
- /*
- if(afref.data[offset] != s[i]) {
- printf("offset: %d, size: %d, diff: %d\n", offset, afref.size, afref.size - offset);
- }
- */
- CPPUNIT_ASSERT_EQUAL(afref.data[offset], s[i]);
- offset++;
- }
- }
-
- printf("done\n");
- }
-
- void singlechannel_nonthreaded()
- {
- printf("\nsinglechannel_nonthreaded()\n");
- const char filename[] = "kit/ride-single-channel.wav";
- int channel = 0;
- bool threaded = false;
- testit(filename, channel, threaded);
- }
-
- void singlechannel_threaded()
- {
- printf("\nsinglechannel_threaded()\n");
- const char filename[] = "kit/ride-single-channel.wav";
- int channel = 0;
- bool threaded = true;
- testit(filename, channel, threaded);
- }
-
- void multichannel_nonthreaded()
- {
- printf("\nmultichannel_nonthreaded()\n");
- const char filename[] = "kit/ride-multi-channel.wav";
- int channel = 0;
- bool threaded = false;
- testit(filename, channel, threaded);
- }
-
- void multichannel_threaded()
- {
- printf("\nmultichannel_threaded()\n");
- 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);
-
-
-