summaryrefslogtreecommitdiff
path: root/src
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 /src
parent2a9dda465230b3b129dc942095a7c8a99ef5fa50 (diff)
Split CacheManager into several AudioCache classes and refactored the lot of them. Unit tests added.
Diffstat (limited to 'src')
-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/cacheaudiofile.h63
-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
16 files changed, 1194 insertions, 760 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/cacheaudiofile.h b/src/cacheaudiofile.h
deleted file mode 100644
index 4f78247..0000000
--- a/src/cacheaudiofile.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- 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 <sndfile.h>
-
-class CacheChannels;
-
-class CacheAudioFile {
- friend class CacheAudioFiles;
-public:
- CacheAudioFile(const std::string& filename);
- ~CacheAudioFile();
-
- size_t getSize() const;
- const std::string& getFilename() const;
-
- 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 CacheAudioFiles {
-public:
- CacheAudioFile& getAudioFile(const std::string filename);
- void release(const std::string filename);
-
-private:
- std::map<std::string, CacheAudioFile*> audiofiles;
-};
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;