From 7788892bfb8aaceaa67fccad1ea4aff325a5ac32 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Tue, 19 Jan 2016 08:32:44 +0100 Subject: Split CacheManager into several AudioCache classes and refactored the lot of them. Unit tests added. --- src/Makefile.am.drumgizmo | 6 +- src/audiocache.cc | 274 +++++++++++++++++++++++ src/audiocache.h | 136 ++++++++++++ src/audiocacheeventhandler.cc | 315 ++++++++++++++++++++++++++ src/audiocacheeventhandler.h | 109 +++++++++ src/audiocachefile.cc | 166 ++++++++++++++ src/audiocachefile.h | 95 ++++++++ src/audiocacheidmanager.cc | 128 +++++++++++ src/audiocacheidmanager.h | 88 ++++++++ src/cacheaudiofile.cc | 156 ------------- src/cacheaudiofile.h | 63 ------ src/cachemanager.cc | 438 ------------------------------------- src/cachemanager.h | 221 ------------------- src/drumgizmo.cc | 16 +- src/drumgizmo.h | 5 +- src/drumkitloader.cc | 1 - src/events.h | 2 +- test/Makefile.am | 56 ++++- test/audiocacheeventhandlertest.cc | 58 +++++ test/audiocachefiletest.cc | 189 ++++++++++++++++ test/audiocacheidmanagertest.cc | 98 +++++++++ test/audiocachetest.cc | 177 +++++++++++++++ test/cachemanagertest.cc | 149 ------------- 23 files changed, 1898 insertions(+), 1048 deletions(-) create mode 100644 src/audiocache.cc create mode 100644 src/audiocache.h create mode 100644 src/audiocacheeventhandler.cc create mode 100644 src/audiocacheeventhandler.h create mode 100644 src/audiocachefile.cc create mode 100644 src/audiocachefile.h create mode 100644 src/audiocacheidmanager.cc create mode 100644 src/audiocacheidmanager.h delete mode 100644 src/cacheaudiofile.cc delete mode 100644 src/cacheaudiofile.h delete mode 100644 src/cachemanager.cc delete mode 100644 src/cachemanager.h create mode 100644 test/audiocacheeventhandlertest.cc create mode 100644 test/audiocachefiletest.cc create mode 100644 test/audiocacheidmanagertest.cc create mode 100644 test/audiocachetest.cc delete mode 100644 test/cachemanagertest.cc diff --git a/src/Makefile.am.drumgizmo b/src/Makefile.am.drumgizmo index be46a3a..89f8024 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/cachemanager.cc \ $(top_srcdir)/src/configuration.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 + +#include +#include +#include + +#include + +#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 eventHandlerLock(eventHandler); + + // NOTE: Not threaded... + //std::lock_guard 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 +#include +#include + +#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()) + , 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 l(mutex); + return files.getFile(filename); +} + +void AudioCacheEventHandler::clearEvents() +{ + std::lock_guard 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 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 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 +#include +#include + +#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* eventqueue; + + bool threaded{false}; + Semaphore sem; + Semaphore sem_run; + bool running{false}; + + AudioCacheIDManager& idManager; + + size_t chunksize{1024}; +}; diff --git a/src/audiocachefile.cc b/src/audiocachefile.cc new file mode 100644 index 0000000..c1fdd7b --- /dev/null +++ b/src/audiocachefile.cc @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * cacheaudiofile.cc + * + * 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. + */ +#include "audiocachefile.h" + +#include + +#include + +#include "audiocache.h" + +AudioCacheFile::AudioCacheFile(const std::string& filename) + : filename(filename) +{ + fh = sf_open(filename.c_str(), SFM_READ, &sf_info); + if(!fh) + { + ERR(audiofile,"SNDFILE Error (%s): %s\n", + filename.c_str(), sf_strerror(fh)); + } + + if(sf_info.frames == 0) + { + printf("sf_info.frames == 0\n"); + } +} + +AudioCacheFile::~AudioCacheFile() +{ + if(fh) + { + sf_close(fh); + fh = nullptr; + } +} + +size_t AudioCacheFile::getSize() const +{ + return sf_info.frames; +} + +const std::string& AudioCacheFile::getFilename() const +{ + return filename; +} + +size_t AudioCacheFile::getChannelCount() +{ + return sf_info.channels; +} + +void AudioCacheFile::readChunk(const CacheChannels& channels, + size_t pos, size_t num_samples) +{ + assert(fh != nullptr); // File handle must never be nullptr + + if(pos > sf_info.frames) + { + printf("pos (%d) > sf_info.frames (%d)\n", pos, (int)sf_info.frames); + return; + } + + sf_seek(fh, pos, SEEK_SET); + + size_t size = sf_info.frames - pos; + if(size > num_samples) + { + size = num_samples; + } + + static sample_t *read_buffer = nullptr; + static size_t read_buffer_size = 0; + + if((size * sf_info.channels) > read_buffer_size) + { + delete[] read_buffer; + read_buffer_size = size * sf_info.channels; + read_buffer = new sample_t[read_buffer_size]; + // TODO: This buffer is never free'd on app shutdown. + } + + size_t read_size = sf_readf_float(fh, read_buffer, size); + (void)read_size; + + for(auto it = channels.begin(); it != channels.end(); ++it) + { + size_t channel = it->channel; + sample_t *data = it->samples; + for (size_t i = 0; i < size; ++i) + { + data[i] = read_buffer[(i * sf_info.channels) + channel]; + } + } + + for(auto it = channels.begin(); it != channels.end(); ++it) + { + *(it->ready) = true; + } +} + +AudioCacheFile& AudioCacheFiles::getFile(const std::string& filename) +{ + AudioCacheFile* cacheAudioFile = nullptr; + + auto it = audiofiles.find(filename); + if(it == audiofiles.end()) + { + cacheAudioFile = new AudioCacheFile(filename); + audiofiles.insert(std::make_pair(filename, cacheAudioFile)); + } + else + { + cacheAudioFile = it->second; + } + + assert(cacheAudioFile); + + // Increase ref count. + ++cacheAudioFile->ref; + + return *cacheAudioFile; +} + +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 + } + + auto audiofile = it->second; + + assert(audiofile->ref); // If ref is not > 0 it shouldn't be in the map. + + --audiofile->ref; + if(audiofile->ref == 0) + { + delete audiofile; + audiofiles.erase(it); + } +} 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 +#include +#include +#include + +#include + +#include + +class CacheChannel { +public: + size_t channel; + sample_t* samples; + size_t num_samples; + volatile bool* ready; +}; + +using CacheChannels = std::list; + +//! 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 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 +#include + +AudioCacheIDManager::AudioCacheIDManager() +{ +} + +AudioCacheIDManager::~AudioCacheIDManager() +{ + assert(availableids.size() == id2cache.size()); // All ids should be released. +} + +void AudioCacheIDManager::init(unsigned int capacity) +{ + std::lock_guard 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 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 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 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::max(); + cache.ready = false; + } + } +} + +std::vector AudioCacheIDManager::getActiveIDs() +{ + std::vector 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 + +#include + +#include + +#include + +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 getActiveIDs(); + + std::mutex mutex; + + std::vector id2cache; + std::vector availableids; +}; diff --git a/src/cacheaudiofile.cc b/src/cacheaudiofile.cc deleted file mode 100644 index 0d4aec9..0000000 --- a/src/cacheaudiofile.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/*************************************************************************** - * cacheaudiofile.cc - * - * 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. - */ -#include "cacheaudiofile.h" - -#include - -#include - -#include - -#include "cachemanager.h" - -CacheAudioFile::CacheAudioFile(const std::string& filename) - : filename(filename) -{ - fh = sf_open(filename.c_str(), SFM_READ, &sf_info); - if(!fh) - { - ERR(audiofile,"SNDFILE Error (%s): %s\n", - filename.c_str(), sf_strerror(fh)); - } -} - -CacheAudioFile::~CacheAudioFile() -{ - if(fh) - { - sf_close(fh); - fh = nullptr; - } -} - -size_t CacheAudioFile::getSize() const -{ - return sf_info.frames; -} - -const std::string& CacheAudioFile::getFilename() const -{ - return filename; -} - -void CacheAudioFile::readChunk(const CacheChannels& channels, - size_t pos, size_t num_samples) -{ - if(fh == nullptr) - { - printf("File handle is null.\n"); - return; - } - - if(pos > sf_info.frames) - { - printf("pos > sf_info.frames\n"); - return; - } - - sf_seek(fh, pos, - SEEK_SET); - - size_t size = sf_info.frames - pos; - if(size > num_samples) - { - size = num_samples; - } - - static sample_t *read_buffer = nullptr; - static size_t read_buffer_size = 0; - - if((size * sf_info.channels) > read_buffer_size) - { - delete[] read_buffer; - read_buffer_size = size * sf_info.channels; - read_buffer = new sample_t[read_buffer_size]; - // TODO: This buffer is never free'd on app shutdown. - } - - size_t read_size = sf_readf_float(fh, read_buffer, size); - (void)read_size; - - for(auto it = channels.begin(); it != channels.end(); ++it) - { - size_t channel = it->channel; - sample_t *data = it->samples; - for (size_t i = 0; i < size; ++i) - { - data[i] = read_buffer[(i * sf_info.channels) + channel]; - } - } - - for(auto it = channels.begin(); it != channels.end(); ++it) - { - *(it->ready) = true; - } -} - -CacheAudioFile& CacheAudioFiles::getAudioFile(const std::string filename) -{ - 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); - } - - auto afile = it->second; - - // Increase ref count. - ++afile->ref; - - return *afile; -} - -void CacheAudioFiles::release(const std::string filename) -{ - auto it = audiofiles.find(filename); - if(it == audiofiles.end()) - { - return; // not open - } - - auto audiofile = it->second; - - assert(audiofile->ref); // If ref is not > 0 it shouldn't be in the map. - - --audiofile->ref; - if(audiofile->ref == 0) - { - delete audiofile; - audiofiles.erase(it); - } -} 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 -#include -#include - -#include - -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 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 -#include -#include - -#include - -#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 -#include -#include - -#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 {}; - -//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 id2cache; - - // Protected by mutex: - std::list eventqueue; - std::list 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 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 240ae9f..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,8 +96,7 @@ private: std::map 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 7dcf0ce..6f3dae6 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,7 +1,8 @@ # Rules for the test code (use `make check` to execute) include $(top_srcdir)/src/Makefile.am.drumgizmo -TESTS = resource engine gui resampler lv2 cachemanager configfile +TESTS = resource engine gui resampler lv2 configfile audiocache \ + audiocachefile audiocacheidmanager audiocacheeventhandler check_PROGRAMS = $(TESTS) @@ -15,19 +16,62 @@ resource_SOURCES = \ test.cc \ resource_test.cc -cachemanager_CXXFLAGS = -DOUTPUT=\"cachemanager\" $(CPPUNIT_CFLAGS) \ +audiocache_CXXFLAGS = -DOUTPUT=\"audiocache\" $(CPPUNIT_CFLAGS) \ +t of them. Unit tests added. -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/test/audiocacheeventhandlertest.cc b/test/audiocacheeventhandlertest.cc new file mode 100644 index 0000000..a8c3777 --- /dev/null +++ b/test/audiocacheeventhandlertest.cc @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocacheeventhandlertest.cc + * + * Thu Jan 7 15:44:14 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 + +#include + +class TestableAudioCacheEventHandler + : public AudioCacheEventHandler +{ +public: + +}; + +class AudioCacheEventHandlerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AudioCacheEventHandlerTest); + CPPUNIT_TEST(threadedTest); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + void threadedTest() + { + AudioCacheIDManager idManager; + idManager.init(10); + + AudioCacheEventHandler eventHandler(idManager); + } +}; + +// 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 + +#include + +#include +#include + +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 + +#include + +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 + +#include +#include +#include + +#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 - -#include -#include - -#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); - - - -- cgit v1.2.3