diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/Makefile.am.drumgizmo | 6 | ||||
-rw-r--r-- | src/audiocache.cc | 276 | ||||
-rw-r--r-- | src/audiocache.h | 113 | ||||
-rw-r--r-- | src/audiocacheeventhandler.cc | 326 | ||||
-rw-r--r-- | src/audiocacheeventhandler.h | 114 | ||||
-rw-r--r-- | src/audiocachefile.cc | 180 | ||||
-rw-r--r-- | src/audiocachefile.h | 98 | ||||
-rw-r--r-- | src/audiocacheidmanager.cc | 124 | ||||
-rw-r--r-- | src/audiocacheidmanager.h | 93 | ||||
-rw-r--r-- | src/audiofile.cc | 277 | ||||
-rw-r--r-- | src/audiofile.h | 72 | ||||
-rw-r--r-- | src/drumgizmo.cc | 1253 | ||||
-rw-r--r-- | src/drumgizmo.h | 71 | ||||
-rw-r--r-- | src/drumkitloader.cc | 230 | ||||
-rw-r--r-- | src/drumkitloader.h | 86 | ||||
-rw-r--r-- | src/events.h | 6 | ||||
-rw-r--r-- | src/mutex.cc | 120 | ||||
-rw-r--r-- | src/mutex.h | 30 | ||||
-rw-r--r-- | src/semaphore.cc | 8 |
20 files changed, 2334 insertions, 1153 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index df9f4ca..cb44909 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,7 @@ EXTRA_DIST = \ channelmixer.h \ chresampler.h \ configuration.h \ + cachemanager.h \ configparser.h \ drumgizmo.h \ drumkit.h \ @@ -39,6 +40,7 @@ EXTRA_DIST = \ audioinputenginemidi.cc \ audiooutputengine.cc \ beatmapper.cc \ + cachemanager.cc \ channel.cc \ channelmixer.cc \ chresampler.cc \ @@ -63,4 +65,4 @@ EXTRA_DIST = \ semaphore.cc \ thread.cc \ velocity.cc \ - versionstr.cc
\ No newline at end of file + versionstr.cc diff --git a/src/Makefile.am.drumgizmo b/src/Makefile.am.drumgizmo index 1a3c857..57b6362 100644 --- a/src/Makefile.am.drumgizmo +++ b/src/Makefile.am.drumgizmo @@ -1,4 +1,8 @@ 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 \ @@ -28,4 +32,4 @@ DRUMGIZMO_SOURCES = \ $(top_srcdir)/src/velocity.cc \ $(top_srcdir)/src/versionstr.cc -DRUMGIZMO_LIBS = $(ZITA_LIBS) $(SNDFILE_LIBS) $(EXPAT_LIBS) $(SAMPLERATE_LIBS)
\ No newline at end of file +DRUMGIZMO_LIBS = $(ZITA_LIBS) $(SNDFILE_LIBS) $(EXPAT_LIBS) $(SAMPLERATE_LIBS) diff --git a/src/audiocache.cc b/src/audiocache.cc new file mode 100644 index 0000000..237a3ff --- /dev/null +++ b/src/audiocache.cc @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocache.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() +{ + DEBUG(cache, "~AudioCache() pre\n"); + + deinit(); + delete[] nodata; + + DEBUG(cache, "~AudioCache() post\n"); +} + +void AudioCache::init(size_t poolsize) +{ + setAsyncMode(true); + + id_manager.init(poolsize); + event_handler.start(); +} + +void AudioCache::deinit() +{ + event_handler.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 = id_manager.registerID({}); + + // If we are out of available 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 = id_manager.getCache(id); + + c.afile = &event_handler.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)]; + } + + event_handler.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 = id_manager.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. + ++number_of_underruns; + 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)]; + } + + event_handler.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 = id_manager.getCache(id); + return cache.ready; +} + +void AudioCache::close(cacheid_t id) +{ + if(id == CACHE_DUMMYID) + { + return; + } + + event_handler.pushCloseEvent(id); +} + +void AudioCache::setFrameSize(size_t framesize) +{ + DEBUG(cache, "%s\n", __PRETTY_FUNCTION__); + + // Make sure the event handler thread is stalled while we set the framesize + // state. + std::lock_guard<AudioCacheEventHandler> event_handler_lock(event_handler); + + // NOTE: Not threaded... + //std::lock_guard<AudioCacheIDManager> id_manager_lock(id_manager); + + 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; + + event_handler.setChunkSize(CHUNKSIZE(framesize)); +} + +size_t AudioCache::frameSize() const +{ + return framesize; +} + +void AudioCache::setAsyncMode(bool async) +{ + event_handler.setThreaded(async); +} + +bool AudioCache::asyncMode() const +{ + return event_handler.getThreaded(); +} + +size_t AudioCache::getNumberOfUnderruns() const +{ + return number_of_underruns; +} + +void AudioCache::resetNumberOfUnderruns() +{ + number_of_underruns = 0; +} diff --git a/src/audiocache.h b/src/audiocache.h new file mode 100644 index 0000000..004fcf8 --- /dev/null +++ b/src/audiocache.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocache.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 + +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 from 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 number_of_underruns{0}; + + AudioCacheIDManager id_manager; + AudioCacheEventHandler event_handler{id_manager}; +}; diff --git a/src/audiocacheeventhandler.cc b/src/audiocacheeventhandler.cc new file mode 100644 index 0000000..7322785 --- /dev/null +++ b/src/audiocacheeventhandler.cc @@ -0,0 +1,326 @@ +/* -*- 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 <assert.h> + +#include <hugin.hpp> + +#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& id_manager) + // Hack to be able to forward declare CacheEvent: + : eventqueue(new std::list<CacheEvent>()) + , id_manager(id_manager) +{ +} + +AudioCacheEventHandler::~AudioCacheEventHandler() +{ + // Close all ids already enqueued to be closed. + clearEvents(); + + auto active_ids = id_manager.getActiveIDs(); + for(auto id : active_ids) + { + 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; + + 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 cache_event; + cache_event.eventType = EventType::LoadNext; + cache_event.pos = pos; + cache_event.afile = afile; + + CacheChannel c; + c.channel = channel; + c.samples = buffer; + + *ready = false; + c.ready = ready; + + cache_event.channels.insert(cache_event.channels.end(), c); + + pushEvent(cache_event); +} + +void AudioCacheEventHandler::pushCloseEvent(cacheid_t id) +{ + CacheEvent cache_event; + cache_event.eventType = EventType::Close; + cache_event.id = id; + + pushEvent(cache_event); +} + +void AudioCacheEventHandler::setChunkSize(size_t chunksize) +{ + DEBUG(cache, "%s\n", __PRETTY_FUNCTION__); + + // We should already locked when this method is called. + //assert(!mutex.try_lock()); + + if(this->chunksize == chunksize) + { + return; + } + + DEBUG(cache, "1)\n"); + + // Remove all events from event queue. + clearEvents(); + + DEBUG(cache, "2)\n"); + + // Skip all active cacheids and make their buffers point at nodata. + id_manager.disableActive(); + + DEBUG(cache, "3)\n"); + + this->chunksize = chunksize; +} + +size_t AudioCacheEventHandler::chunkSize() +{ + return chunksize; +} + +AudioCacheFile& AudioCacheEventHandler::openFile(const std::string& filename) +{ + std::lock_guard<std::mutex> lock(mutex); + return files.getFile(filename); +} + +void AudioCacheEventHandler::clearEvents() +{ + // Iterate all events ignoring load events and handling close events. + for(auto& event : *eventqueue) + { + if(event.eventType == EventType::Close) + { + handleCloseCache(event.id); // This method does not lock. + } + } + + eventqueue->clear(); +} + +void AudioCacheEventHandler::handleLoadNextEvent(CacheEvent& cache_event) +{ + cache_event.afile->readChunk(cache_event.channels, cache_event.pos, + chunksize); +} + +void AudioCacheEventHandler::handleCloseEvent(CacheEvent& cache_event) +{ + std::lock_guard<std::mutex> lock(mutex); + handleCloseCache(cache_event.id); +} + +void AudioCacheEventHandler::handleCloseCache(cacheid_t cacheid) +{ + auto& cache = id_manager.getCache(cacheid); + + files.releaseFile(cache.afile->getFilename()); + + delete[] cache.front; + delete[] cache.back; + + id_manager.releaseID(cacheid); +} + +void AudioCacheEventHandler::handleEvent(CacheEvent& cache_event) +{ + switch(cache_event.eventType) + { + case EventType::LoadNext: + handleLoadNextEvent(cache_event); + break; + case EventType::Close: + handleCloseEvent(cache_event); + 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 cache_event = eventqueue->front(); + eventqueue->pop_front(); + mutex.unlock(); + + // TODO: Skip event if cache_event.pos < cache.pos + //if(!cache_event.active) + //{ + // continue; + //} + + handleEvent(cache_event); + } +} + +void AudioCacheEventHandler::pushEvent(CacheEvent& cache_event) +{ + if(!threaded) + { + handleEvent(cache_event); + return; + } + + { + std::lock_guard<std::mutex> lock(mutex); + + bool found = false; + + if(cache_event.eventType == EventType::LoadNext) + { + for(auto& queued_event : *eventqueue) + { + if((queued_event.eventType == EventType::LoadNext) && + (cache_event.afile->getFilename() == + queued_event.afile->getFilename()) && + (cache_event.pos == queued_event.pos)) + { + // Append channel and buffer to the existing event. + queued_event.channels.insert(queued_event.channels.end(), + cache_event.channels.begin(), + cache_event.channels.end()); + found = true; + break; + } + } + } + + if(!found) + { + // The event was not already on the list, create a new one. + eventqueue->push_back(cache_event); + } + } + + sem.post(); +} diff --git a/src/audiocacheeventhandler.h b/src/audiocacheeventhandler.h new file mode 100644 index 0000000..daf7bb9 --- /dev/null +++ b/src/audiocacheeventhandler.h @@ -0,0 +1,114 @@ +/* -*- 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 "mutex.h" + +#include "audiocachefile.h" +#include "audiocacheidmanager.h" + +class CacheEvent; + +class AudioCacheEventHandler + : protected Thread +{ +public: + AudioCacheEventHandler(AudioCacheIDManager& id_manager); + ~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: + void clearEvents(); + + void handleLoadNextEvent(CacheEvent& cache_event); + + //! Lock the mutex and calls handleCloseCache + void handleCloseEvent(CacheEvent& cache_event); + + //! Close decrease the file ref and release the cache id. + void handleCloseCache(cacheid_t cacheid); + + void handleEvent(CacheEvent& cache_event); + + // From Thread + void thread_main() override; + + void pushEvent(CacheEvent& cache_event); + + AudioCacheFiles files; + + std::mutex mutex; + + std::list<CacheEvent>* eventqueue; + + bool threaded{false}; + Semaphore sem; + Semaphore sem_run; + bool running{false}; + + AudioCacheIDManager& id_manager; + + size_t chunksize{1024}; +}; diff --git a/src/audiocachefile.cc b/src/audiocachefile.cc new file mode 100644 index 0000000..916ecb7 --- /dev/null +++ b/src/audiocachefile.cc @@ -0,0 +1,180 @@ +/* -*- 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 <assert.h> + +#include <hugin.hpp> + +#include <cstring> + +#include "audiocache.h" + +AudioCacheFile::AudioCacheFile(const std::string& filename) + : filename(filename) +{ + std::memset(&sf_info, 0, sizeof(SF_INFO)); + + 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)); + return; + } + + 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(!fh) + { + return; + } + + if((int)pos > sf_info.frames) + { + WARN(cache, "pos (%d) > sf_info.frames (%d)\n", + (int)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) +{ + std::lock_guard<std::mutex> lock(mutex); + + 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) +{ + std::lock_guard<std::mutex> lock(mutex); + + 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..9910563 --- /dev/null +++ b/src/audiocachefile.h @@ -0,0 +1,98 @@ +/* -*- 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 "mutex.h" + +#include <sndfile.h> + +#include <audiotypes.h> + +//! Channel data container in the cache world. +class CacheChannel { +public: + size_t channel; //< Channel number + sample_t* samples; //< Sample buffer pointer. + size_t num_samples; //< Number of samples in the sample buffer + volatile bool* ready; //< Is set to tru when the loading is done. +}; + +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..a3e16a0 --- /dev/null +++ b/src/audiocacheidmanager.cc @@ -0,0 +1,124 @@ +/* -*- 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() +{ + 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 < (int)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> active_ids; + + for(auto& cache : id2cache) + { + if(cache.id != CACHE_NOID) + { + active_ids.push_back(cache.id); + } + } + + return active_ids; +} diff --git a/src/audiocacheidmanager.h b/src/audiocacheidmanager.h new file mode 100644 index 0000000..70f7ce1 --- /dev/null +++ b/src/audiocacheidmanager.h @@ -0,0 +1,93 @@ +/* -*- 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> +#include "mutex.h" + +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() = default; + ~AudioCacheIDManager(); + + //! Initialise id lists with specified capacity. + //! Exceeding this capacity will result in CACHE_DUMMYID on calls to + //! registerID. + void init(unsigned int capacity); + + //! Get the cache object connected with the specified cacheid. + //! Note: The cacheid MUST be active. + cache_t& getCache(cacheid_t id); + + //! Reserve a new cache object and return its cacheid. + //! The contents of the supplied cache object will be copied to the new + //! cache object. + cacheid_t registerID(const cache_t& cache); + + //! Release a cache object and its correseponding cacheid. + //! After this call the cacheid can no longer be used. + 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/audiofile.cc b/src/audiofile.cc index 59e0c14..e9b5976 100644 --- a/src/audiofile.cc +++ b/src/audiofile.cc @@ -38,223 +38,116 @@ #include "configuration.h" -AudioFile::AudioFile(std::string filename, int filechannel) +AudioFile::AudioFile(const std::string& filename, int filechannel) { - is_loaded = false; - this->filename = filename; - this->filechannel = filechannel; + is_loaded = false; + this->filename = filename; + this->filechannel = filechannel; - data = NULL; - size = 0; + data = nullptr; + size = 0; -#ifdef LAZYLOAD - preloaded_data = NULL; -#endif/*LAZYLOAD*/ - - magic = this; + magic = this; } AudioFile::~AudioFile() { - magic = NULL; - unload(); + magic = nullptr; + unload(); } bool AudioFile::isValid() { - return this == magic; + return this == magic; } void AudioFile::unload() { - // Make sure we don't unload the object while loading it... - MutexAutolock l(mutex); + // Make sure we don't unload the object while loading it... + MutexAutolock l(mutex); - is_loaded = false; + is_loaded = false; -#ifdef LAZYLOAD - if(data == preloaded_data) { - delete[] data; - data = NULL; - size = 0; - } else { - size = 0; - delete[] data; - data = NULL; - delete preloaded_data; - preloaded_data = NULL; - } -#else - delete[] data; - data = NULL; - size = 0; -#endif/*LAZYLOAD*/ + delete[] data; + data = nullptr; + size = 0; } #define BUFFER_SIZE 4092 void AudioFile::load(int num_samples) { - // Make sure we don't unload the object while loading it... - MutexAutolock l(mutex); - - /* - Lazy load of drum kits - init(); - return; - */ - - if(data) return; - - SF_INFO sf_info; - SNDFILE *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)); - return; - } - - size = sf_info.frames; - - double ratio = (double)Conf::samplerate / (double)sf_info.samplerate; - - if(num_samples != ALL_SAMPLES) { - // Make sure we read enough samples, even after conversion. - num_samples /= ratio; - if((int)size > num_samples) size = num_samples; - } - - sample_t* data = new sample_t[size]; - if(sf_info.channels == 1) { - size = sf_read_float(fh, data, size); - } - else { - // check filechannel exists - if(filechannel >= sf_info.channels) { - filechannel = sf_info.channels - 1; - } - sample_t buffer[BUFFER_SIZE]; - int readsize = BUFFER_SIZE / sf_info.channels; - int totalread = 0; - int read; - do { - read = sf_readf_float(fh, buffer, readsize); - for (int i = 0; i < read; i++) { - data[totalread++] = buffer[i * sf_info.channels + filechannel]; - } - } while(read > 0 && totalread < (int)size); - // set data size to total bytes read - size = totalread; - } - - DEBUG(audiofile,"Loaded %d samples %p\n", (int)size, this); - - sf_close(fh); - - this->data = data; - is_loaded = true; - - //DEBUG(audiofile, "Loading of %s completed.\n", filename.c_str()); + // Make sure we don't unload the object while loading it... + MutexAutolock l(mutex); + + if(data) + { + return; + } + + SF_INFO sf_info; + SNDFILE *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)); + return; + } + + if(num_samples == ALL_SAMPLES) + { + num_samples = sf_info.frames; + } + + size = sf_info.frames; + preloadedsize = sf_info.frames; + + if(preloadedsize > (size_t)num_samples) + { + preloadedsize = num_samples; + } + + sample_t* data = new sample_t[preloadedsize]; + if(sf_info.channels == 1) + { + preloadedsize = sf_read_float(fh, data, preloadedsize); + } + else + { + // check filechannel exists + if(filechannel >= sf_info.channels) + { + filechannel = sf_info.channels - 1; + } + + sample_t buffer[BUFFER_SIZE]; + int readsize = BUFFER_SIZE / sf_info.channels; + int totalread = 0; + int read; + + do + { + read = sf_readf_float(fh, buffer, readsize); + for(int i = 0; (i < read) && (totalread < num_samples); ++i) + { + data[totalread++] = buffer[i * sf_info.channels + filechannel]; + } + } + while( (read > 0) && + (totalread < (int)preloadedsize) && + (totalread < num_samples) ); + + // set data size to total bytes read + preloadedsize = totalread; + } + + sf_close(fh); + + this->data = data; + is_loaded = true; } bool AudioFile::isLoaded() { - return is_loaded; -} - -#ifdef LAZYLOAD -#define SIZE 512*4 -void AudioFile::init() -{ - //DEBUG(audiofile,"Initializing %p\n", this); - if(data) { - //DEBUG(audiofile,"\t already initialized\n"); - return; - } - - SF_INFO sf_info; - SNDFILE *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)); - return; - } - - int size = SIZE; - - sample_t* data = new sample_t[size]; - - size = sf_read_float(fh, data, size); - - //DEBUG(audiofile,"Lazy loaded %d samples\n", size); - sf_close(fh); - - mutex.lock(); - this->data = data; - this->size = size; - this->preloaded_data = data; - this->is_loaded = true; - mutex.unlock(); -} - -void AudioFile::loadNext() -{ - if(this->data != this->preloaded_data) { - //DEBUG(audiofile,"Already completely loaded %p\n", this); - return; - } - - SF_INFO sf_info; - SNDFILE *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)); - return; - } - - int r; -// int size_accum = 0; - sample_t* data = new sample_t[sf_info.frames]; - memcpy(data, this->preloaded_data, this->size * sizeof(sample_t)); - this->data = data; - sf_seek(fh, this->size, SEEK_SET); -// sample_t* data_buf = new sample_t[SIZE]; - while(this->size < sf_info.frames) { - //DEBUG(audiofile,"Accumulated %d of %llu\n", size_accum, sf_info.frames); - //if( (r = sf_read_float(fh, data_buf, SIZE)) < 0) { - if( (r = sf_read_float(fh, &data[this->size], SIZE)) < 0) { - ERR(audiofile,"Error reading sound file\n"); - break; - } - //size_accum += r; - //memcpy(data+size_accum, data_buf, sizeof(sample_t) * r); - this->size += r; - } - //delete data_buf; - - //DEBUG(audiofile,"Finished loading %d samples %p\n", size, this); - sf_close(fh); - - //mutex.lock(); - //this->data = data; - //this->size = size; - //mutex.unlock(); -} - -void AudioFile::reset() -{ - //DEBUG(audiofile,"Resetting audio file %p\n", this); - if(this->data == this->preloaded_data) { - //DEBUG(audiofile,"\tNot completely loaded - skipping %p\n", this); - return; - } - - mutex.lock(); - volatile sample_t* old_data = data; - this->size = SIZE; - this->data = this->preloaded_data; - //DEBUG(audiofile,"Deleting data %p\n", this); - delete old_data; - mutex.unlock(); + return is_loaded; } -#endif diff --git a/src/audiofile.h b/src/audiofile.h index 98bf101..3ca8b97 100644 --- a/src/audiofile.h +++ b/src/audiofile.h @@ -24,8 +24,7 @@ * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ -#ifndef __DRUMGIZMO_AUDIOFILE_H__ -#define __DRUMGIZMO_AUDIOFILE_H__ +#pragma once #include <string> #include <map> @@ -36,72 +35,31 @@ #include "mutex.h" #include "audio.h" -/* - Plan for lazy loading of audio (Brainstorming) - * Encapsulate data array? - - Speed issues? - - Other suggestion - * Trigger on read begin and read done - - readnext(instrument)? - * size_t current latest loaded sample - * run in own thread? threads in drumgizmo?? - - Add soundfile-loader-class which run in its own thread - * Add pre-loading constant - * Pointer to pos in audio stream (maybe just last position read) - * Strategy for how to handle pre-loading of remaining file - - Is it acceptable only to handle sequential reading of data (no random access)? - - Thread A Thread B - - :preload constant (user defined) - :speed modifier constant (in which time must - sample n be loaded relative to trigger time) - ---------- ------ - | Loader | <------- Trigger load of InstrumentSample n --------- | DG | - ---------- ------ - Load (int- right most loaded sample --> If current sample pos loaded - | --------- | | - Wave Into --> | SndFile | <----- Read data (directly from array) - --------- -*/ - -//#define LAZYLOAD - #define ALL_SAMPLES -1 class AudioFile { public: - AudioFile(std::string filename, int filechannel); - ~AudioFile(); + AudioFile(const std::string& filename, int filechannel); + ~AudioFile(); - void load(int num_samples = ALL_SAMPLES); - void unload(); + void load(int num_samples = ALL_SAMPLES); + void unload(); - bool isLoaded(); + bool isLoaded(); - volatile size_t size; - volatile sample_t *data; + volatile size_t size{0}; // Full size of the file + volatile size_t preloadedsize{0}; // Number of samples preloaded (in data) + sample_t *data{nullptr}; - std::string filename; + std::string filename; -#ifdef LAZYLOAD -// SF_INFO sf_info; -// SNDFILE *fh; -// bool completely_loaded; - void init(); - void reset(); - void loadNext(); - sample_t* preloaded_data; -#endif/*LAZYLOAD*/ + bool isValid(); - bool isValid(); + Mutex mutex; - Mutex mutex; + int filechannel; private: - void *magic; - volatile bool is_loaded; - int filechannel; + void *magic; + volatile bool is_loaded; }; - -#endif/*__DRUMGIZMO_AUDIOFILE_H__*/ diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc index 7ce05ef..a777125 100644 --- a/src/drumgizmo.cc +++ b/src/drumgizmo.cc @@ -46,718 +46,711 @@ #include "nolocale.h" DrumGizmo::DrumGizmo(AudioOutputEngine *o, AudioInputEngine *i) - : MessageReceiver(MSGRCV_ENGINE), - loader(), oe(o), ie(i) + : MessageReceiver(MSGRCV_ENGINE) + , loader() + , oe(o) + , ie(i) + , framesize(0) + , freewheel(false) { - is_stopping = false; + is_stopping = false; + audioCache.init(1000); // start thread } DrumGizmo::~DrumGizmo() { + audioCache.deinit(); // stop thread } bool DrumGizmo::loadkit(std::string file) { - if(file == "") return 1; + if(file == "") + { + return 1; + } - DEBUG(drumgizmo, "loadkit(%s)\n", file.c_str()); + DEBUG(drumgizmo, "loadkit(%s)\n", file.c_str()); - // Remove all queue AudioFiles from loader before we actually delete them. - loader.skip(); + // Remove all queue AudioFiles from loader before we actually delete them. + loader.skip(); - // Delete all Channels, Instruments, Samples and AudioFiles. - kit.clear(); + // Delete all Channels, Instruments, Samples and AudioFiles. + kit.clear(); - DrumKitParser parser(file, kit); - if(parser.parse()) { - ERR(drumgizmo, "Drumkit parser failed: %s\n", file.c_str()); - return false; - } + DrumKitParser parser(file, kit); + if(parser.parse()) + { + ERR(drumgizmo, "Drumkit parser failed: %s\n", file.c_str()); + return false; + } - loader.loadKit(&kit); + loader.loadKit(&kit); #ifdef WITH_RESAMPLER - for(int i = 0; i < MAX_NUM_CHANNELS; i++) { - resampler[i].setup(kit.samplerate(), Conf::samplerate); - } + for(int i = 0; i < MAX_NUM_CHANNELS; ++i) + { + resampler[i].setup(kit.samplerate(), Conf::samplerate); + } #endif/*WITH_RESAMPLER*/ - DEBUG(loadkit, "loadkit: Success\n"); + DEBUG(loadkit, "loadkit: Success\n"); - return true; + return true; } bool DrumGizmo::init() { - if(!ie->init(kit.instruments)) return false; - if(!oe->init(kit.channels)) return false; + if(!ie->init(kit.instruments)) + { + return false; + } - return true; + if(!oe->init(kit.channels)) + { + return false; + } + + return true; } void DrumGizmo::handleMessage(Message *msg) { - DEBUG(msg, "got message."); - switch(msg->type()) { - case Message::LoadDrumKit: - { - DEBUG(msg, "got LoadDrumKitMessage message."); - LoadDrumKitMessage *m = (LoadDrumKitMessage*)msg; - loadkit(m->drumkitfile); - //init(true); - } - break; - case Message::LoadMidimap: - DEBUG(msg, "got LoadMidimapMessage message."); - if(!ie->isMidiEngine()) break; - { - AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; - LoadMidimapMessage *m = (LoadMidimapMessage*)msg; - bool ret = aim->loadMidiMap(m->midimapfile, kit.instruments); - - LoadStatusMessageMidimap *ls = new LoadStatusMessageMidimap(); - ls->success = ret; - msghandler.sendMessage(MSGRCV_UI, ls); - } - break; - case Message::EngineSettingsMessage: - { - bool mmap_loaded = false; - std::string mmapfile; - if(ie->isMidiEngine()) { - AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; - mmapfile = aim->midimapFile(); - mmap_loaded = aim->isValid(); - } - - EngineSettingsMessage *msg = new EngineSettingsMessage(); - msg->midimapfile = mmapfile; - msg->midimap_loaded = mmap_loaded; - msg->drumkitfile = kit.file(); - msg->drumkit_loaded = loader.isDone(); - msg->enable_velocity_modifier = Conf::enable_velocity_modifier; - msg->velocity_modifier_falloff = Conf::velocity_modifier_falloff; - msg->velocity_modifier_weight = Conf::velocity_modifier_weight; - msg->enable_velocity_randomiser = Conf::enable_velocity_randomiser; - msg->velocity_randomiser_weight = Conf::velocity_randomiser_weight; - msghandler.sendMessage(MSGRCV_UI, msg); - } - break; - case Message::ChangeSettingMessage: - { - ChangeSettingMessage *ch = (ChangeSettingMessage*)msg; - switch(ch->name) { - case ChangeSettingMessage::enable_velocity_modifier: - Conf::enable_velocity_modifier = ch->value; - break; - case ChangeSettingMessage::velocity_modifier_weight: - Conf::velocity_modifier_weight = ch->value; - break; - case ChangeSettingMessage::velocity_modifier_falloff: - Conf::velocity_modifier_falloff = ch->value; - break; - } - } - break; - default: - break; - } + DEBUG(msg, "got message."); + switch(msg->type()) { + case Message::LoadDrumKit: + { + DEBUG(msg, "got LoadDrumKitMessage message."); + LoadDrumKitMessage *m = (LoadDrumKitMessage*)msg; + loadkit(m->drumkitfile); + //init(true); + } + break; + case Message::LoadMidimap: + DEBUG(msg, "got LoadMidimapMessage message."); + if(!ie->isMidiEngine()) + { + break; + } + { + AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; + LoadMidimapMessage *m = (LoadMidimapMessage*)msg; + bool ret = aim->loadMidiMap(m->midimapfile, kit.instruments); + + LoadStatusMessageMidimap *ls = new LoadStatusMessageMidimap(); + ls->success = ret; + msghandler.sendMessage(MSGRCV_UI, ls); + } + break; + case Message::EngineSettingsMessage: + { + bool mmap_loaded = false; + std::string mmapfile; + if(ie->isMidiEngine()) + { + AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; + mmapfile = aim->midimapFile(); + mmap_loaded = aim->isValid(); + } + + EngineSettingsMessage *msg = new EngineSettingsMessage(); + msg->midimapfile = mmapfile; + msg->midimap_loaded = mmap_loaded; + msg->drumkitfile = kit.file(); + msg->drumkit_loaded = loader.isDone(); + msg->enable_velocity_modifier = Conf::enable_velocity_modifier; + msg->velocity_modifier_falloff = Conf::velocity_modifier_falloff; + msg->velocity_modifier_weight = Conf::velocity_modifier_weight; + msg->enable_velocity_randomiser = Conf::enable_velocity_randomiser; + msg->velocity_randomiser_weight = Conf::velocity_randomiser_weight; + msghandler.sendMessage(MSGRCV_UI, msg); + } + break; + case Message::ChangeSettingMessage: + { + ChangeSettingMessage *ch = (ChangeSettingMessage*)msg; + switch(ch->name) { + case ChangeSettingMessage::enable_velocity_modifier: + Conf::enable_velocity_modifier = ch->value; + break; + case ChangeSettingMessage::velocity_modifier_weight: + Conf::velocity_modifier_weight = ch->value; + break; + case ChangeSettingMessage::velocity_modifier_falloff: + Conf::velocity_modifier_falloff = ch->value; + break; + } + } + break; + default: + break; + } } -bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples) +void DrumGizmo::setFrameSize(size_t framesize) { - // Handle engine messages, at most one in each iteration: - handleMessages(1); - - ie->pre(); - oe->pre(nsamples); - - // - // Read new events - // - - //DEBUG(engine, "Number of active events: %d\n", activeevents[0].size()); - - size_t nev; - event_t *evs = ie->run(pos, nsamples, &nev); - - for(size_t e = 0; e < nev; e++) { - if(evs[e].type == TYPE_ONSET) { - Instrument *i = NULL; - int d = evs[e].instrument; - /* - Instruments::iterator it = kit.instruments.begin(); - while(d-- && it != kit.instruments.end()) { - i = &(it->second); - it++; - } - */ - - if(!kit.isValid()) continue; - - if(d < (int)kit.instruments.size()) { - i = kit.instruments[d]; - } - - if(i == NULL || !i->isValid()) { - ERR(drumgizmo, "Missing Instrument %d.\n", evs[e].instrument); - continue; - } - - if(i->group() != "") { - // Add event to ramp down all existing events with the same groupname. - Channels::iterator j = kit.channels.begin(); - while(j != kit.channels.end()) { - Channel &ch = *j; - std::list< Event* >::iterator evs = activeevents[ch.num].begin(); - while(evs != activeevents[ch.num].end()) { - Event *ev = *evs; - if(ev->type() == Event::sample) { - EventSample *sev = (EventSample*)ev; - if(sev->group == i->group() && sev->instrument != i) { - sev->rampdown = 3000; // Ramp down 3000 samples - // TODO: This must be configurable at some point... - // ... perhaps even by instrument (ie. in the xml file) - sev->ramp_start = sev->rampdown; - } - } - evs++; - } - j++; - } - } - - Sample *s = i->sample(evs[e].velocity, evs[e].offset + pos); - - if(s == NULL) { - ERR(drumgizmo, "Missing Sample.\n"); - continue; - } - - Channels::iterator j = kit.channels.begin(); - while(j != kit.channels.end()) { - Channel &ch = *j; - AudioFile *af = s->getAudioFile(&ch); - if(af) { - // LAZYLOAD: - // DEBUG(drumgizmo,"Requesting preparing of audio file\n"); - // loader.prepare(af); - } - if(af == NULL || !af->isValid()) { - //DEBUG(drumgizmo,"Missing AudioFile.\n"); - } else { - //DEBUG(drumgizmo, "Adding event %d.\n", evs[e].offset); - Event *evt = new EventSample(ch.num, 1.0, af, i->group(), i); - evt->offset = (evs[e].offset + pos) * resampler[0].ratio(); - activeevents[ch.num].push_back(evt); - } - j++; - } - } - - if(evs[e].type == TYPE_STOP) { - is_stopping = true; - } - - if(is_stopping) { - // Count the number of active events. - int num_active_events = 0; - Channels::iterator j = kit.channels.begin(); - while(j != kit.channels.end()) { - Channel &ch = *j; - num_active_events += activeevents[ch.num].size(); - j++; - } - - if(num_active_events == 0) { - // No more active events - now we can stop the engine. - return false; - } - } - - } - - free(evs); - - // - // Write audio - // -#ifdef WITH_RESAMPLER - if(Conf::enable_resampling == false || - resampler[0].ratio() == 1.0) { // No resampling needed -#endif - for(size_t c = 0; c < kit.channels.size(); c++) { - sample_t *buf = samples; - bool internal = false; - if(oe->getBuffer(c)) { - buf = oe->getBuffer(c); - internal = true; - } - if(buf) { - memset(buf, 0, nsamples * sizeof(sample_t)); - - getSamples(c, pos, buf, nsamples); - - if(!internal) oe->run(c, samples, nsamples); - } - } -#ifdef WITH_RESAMPLER - } else { - // Resampling needed - - // - // NOTE: Channels must be processed one buffer at a time on all channels in - // parallel - NOT all buffers on one channel and then all buffer on the next - // one since this would mess up the event queue (it would jump back and forth - // in time) - // - - // Prepare output buffer - for(size_t c = 0; c < kit.channels.size(); c++) { - resampler[c].setOutputSamples(resampler_output_buffer[c], nsamples); - } - - // Process channel data - size_t kitpos = pos * resampler[0].ratio(); - size_t insize = sizeof(resampler_input_buffer[0]) / sizeof(sample_t); - - //printf("ratio: %f\n", resampler[c].ratio()); - while(resampler[0].getOutputSampleCount() > 0) { - for(size_t c = 0; c < kit.channels.size(); c++) { - if(resampler[c].getInputSampleCount() == 0) { - sample_t *sin = resampler_input_buffer[c]; - memset(resampler_input_buffer[c], 0, - sizeof(resampler_input_buffer[c])); - getSamples(c, kitpos, sin, insize); - - resampler[c].setInputSamples(sin, insize); - } - resampler[c].process(); - } - kitpos += insize; - } - - // Write output data to output engine. - for(size_t c = 0; c < kit.channels.size(); c++) { - oe->run(c, resampler_output_buffer[c], nsamples); - } - - } -#endif/*WITH_RESAMPLER*/ - - ie->post(); - oe->post(nsamples); - - pos += nsamples; + // If we are resampling override the frame size. + if(resampler[0].ratio() != 1) + { + framesize = RESAMPLER_INPUT_BUFFER; + } + + if(this->framesize != framesize) + { + DEBUG(drumgizmo, "New framesize: %d\n", (int)framesize); + + this->framesize = framesize; + + // Update framesize in drumkitloader and cachemanager: + loader.setFrameSize(framesize); + audioCache.setFrameSize(framesize); + } +} - return true; +void DrumGizmo::setFreeWheel(bool freewheel) +{ + // Freewheel = true means that we are bouncing and therefore running faster + // than realtime. + if(freewheel != this->freewheel) + { + this->freewheel = freewheel; + audioCache.setAsyncMode(!freewheel); + } } void DrumGizmo::run(int endpos) { - size_t pos = 0; - size_t nsamples = oe->getBufferSize(); - sample_t *samples = (sample_t *)malloc(nsamples * sizeof(sample_t)); + size_t pos = 0; + size_t nsamples = oe->getBufferSize(); + sample_t *samples = (sample_t *)malloc(nsamples * sizeof(sample_t)); + + setFrameSize(oe->getBufferSize()); + + ie->start(); + oe->start(); - ie->start(); - oe->start(); + while(run(pos, samples, nsamples) == true) + { + pos += nsamples; + if((endpos != -1) && (pos >= (size_t)endpos)) + { + break; + } + } + + ie->stop(); + oe->stop(); + + free(samples); +} + +bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples) +{ + setFrameSize(nsamples); + + // Handle engine messages, at most one in each iteration: + handleMessages(1); + + ie->pre(); + oe->pre(nsamples); + + // + // Read new events + // + + //DEBUG(engine, "Number of active events: %d\n", activeevents[0].size()); + + size_t nev; + event_t *evs = ie->run(pos, nsamples, &nev); + + for(size_t e = 0; e < nev; ++e) + { + if(evs[e].type == TYPE_ONSET) + { + Instrument *i = nullptr; + int d = evs[e].instrument; + /* + Instruments::iterator it = kit.instruments.begin(); + while(d-- && it != kit.instruments.end()) + { + i = &(it->second); + ++it; + } + */ + + if(!kit.isValid()) + { + continue; + } + + if(d < (int)kit.instruments.size()) + { + i = kit.instruments[d]; + } + + if(i == nullptr || !i->isValid()) + { + ERR(drumgizmo, "Missing Instrument %d.\n", evs[e].instrument); + continue; + } + + if(i->group() != "") + { + // Add event to ramp down all existing events with the same groupname. + Channels::iterator j = kit.channels.begin(); + while(j != kit.channels.end()) + { + Channel &ch = *j; + std::list< Event* >::iterator evs = activeevents[ch.num].begin(); + while(evs != activeevents[ch.num].end()) + { + Event *ev = *evs; + if(ev->type() == Event::sample) + { + EventSample *sev = (EventSample*)ev; + if(sev->group == i->group() && sev->instrument != i) + { + sev->rampdown = 3000; // Ramp down 3000 samples + // TODO: This must be configurable at some point... + // ... perhaps even by instrument (ie. in the xml file) + sev->ramp_start = sev->rampdown; + } + } + ++evs; + } + ++j; + } + } + + Sample *s = i->sample(evs[e].velocity, evs[e].offset + pos); + + if(s == nullptr) + { + ERR(drumgizmo, "Missing Sample.\n"); + continue; + } + + Channels::iterator j = kit.channels.begin(); + while(j != kit.channels.end()) + { + Channel &ch = *j; + AudioFile *af = s->getAudioFile(&ch); + if(af) + { + // LAZYLOAD: + // DEBUG(drumgizmo,"Requesting preparing of audio file\n"); + // loader.prepare(af); + } + if(af == nullptr || !af->isValid()) + { + //DEBUG(drumgizmo,"Missing AudioFile.\n"); + } + else + { + //DEBUG(drumgizmo, "Adding event %d.\n", evs[e].offset); + Event *evt = new EventSample(ch.num, 1.0, af, i->group(), i); + evt->offset = (evs[e].offset + pos) * resampler[0].ratio(); + activeevents[ch.num].push_back(evt); + } + ++j; + } + } + + if(evs[e].type == TYPE_STOP) + { + is_stopping = true; + } + + if(is_stopping) + { + // Count the number of active events. + int num_active_events = 0; + Channels::iterator j = kit.channels.begin(); + while(j != kit.channels.end()) + { + Channel &ch = *j; + num_active_events += activeevents[ch.num].size(); + ++j; + } + + if(num_active_events == 0) + { + // No more active events - now we can stop the engine. + return false; + } + } + + } + + free(evs); + + // + // Write audio + // +#ifdef WITH_RESAMPLER + if((Conf::enable_resampling == false) || + (resampler[0].ratio() == 1.0)) // No resampling needed + { +#endif + for(size_t c = 0; c < kit.channels.size(); ++c) + { + sample_t *buf = samples; + bool internal = false; + if(oe->getBuffer(c)) + { + buf = oe->getBuffer(c); + internal = true; + } + + if(buf) + { + memset(buf, 0, nsamples * sizeof(sample_t)); + + getSamples(c, pos, buf, nsamples); + + if(!internal) + { + oe->run(c, samples, nsamples); + } + } + } +#ifdef WITH_RESAMPLER + } + else + { + // Resampling needed + + // + // NOTE: Channels must be processed one buffer at a time on all channels in + // parallel - NOT all buffers on one channel and then all buffer on the next + // one since this would mess up the event queue (it would jump back and + // forth in time) + // + + // Prepare output buffer + for(size_t c = 0; c < kit.channels.size(); ++c) + { + resampler[c].setOutputSamples(resampler_output_buffer[c], nsamples); + } + + // Process channel data + size_t kitpos = pos * resampler[0].ratio(); + size_t insize = sizeof(resampler_input_buffer[0]) / sizeof(sample_t); + + while(resampler[0].getOutputSampleCount() > 0) + { + for(size_t c = 0; c < kit.channels.size(); ++c) + { + if(resampler[c].getInputSampleCount() == 0) + { + sample_t *sin = resampler_input_buffer[c]; + memset(resampler_input_buffer[c], 0, + sizeof(resampler_input_buffer[c])); + getSamples(c, kitpos, sin, insize); + + resampler[c].setInputSamples(sin, insize); + } + resampler[c].process(); + } + kitpos += insize; + } + + // Write output data to output engine. + for(size_t c = 0; c < kit.channels.size(); ++c) + { + oe->run(c, resampler_output_buffer[c], nsamples); + } + + } +#endif/*WITH_RESAMPLER*/ - while(run(pos, samples, nsamples) == true) { - pos += nsamples; - if(endpos != -1 && pos >= (size_t)endpos) break; - } + ie->post(); + oe->post(nsamples); - ie->stop(); - oe->stop(); + pos += nsamples; - free(samples); + return true; } +#undef SSE // SSE broken for now ... so disable it. #ifdef SSE #define N 8 -typedef float vNsf __attribute__ ((vector_size(sizeof(float)*N))); +typedef float vNsf __attribute__ ((vector_size(sizeof(sample_t)*N))); #endif/*SSE*/ void DrumGizmo::getSamples(int ch, int pos, sample_t *s, size_t sz) { - std::list< Event* >::iterator i = activeevents[ch].begin(); - while(i != activeevents[ch].end()) { - bool removeevent = false; - - Event *event = *i; - - Event::type_t type = event->type(); - switch(type) { - case Event::sample: - { - EventSample *evt = (EventSample *)event; - AudioFile *af = evt->file; - - if(!af->isLoaded() || !af->isValid() || s == NULL) { - removeevent = true; - break; - } - - { - MutexAutolock l(af->mutex); - - size_t n = 0; - if(evt->offset > (size_t)pos) n = evt->offset - pos; - size_t end = sz; - if((evt->t + end - n) > af->size) end = af->size - evt->t + n; - if(end > sz) end = sz; - - if(evt->rampdown == NO_RAMPDOWN) { + std::list< Event* >::iterator i = activeevents[ch].begin(); + for(; i != activeevents[ch].end(); ++i) + { + bool removeevent = false; + + Event *event = *i; + + Event::type_t type = event->type(); + switch(type) { + case Event::sample: + { + EventSample *evt = (EventSample *)event; + AudioFile *af = evt->file; + + if(!af->isLoaded() || !af->isValid() || (s == nullptr)) + { + removeevent = true; + break; + } + + // Don't handle event now is is scheduled for a future iteration? + if(evt->offset > (pos + sz)) + { + continue; + } + + if(evt->cache_id == CACHE_NOID) + { + size_t initial_chunksize = (pos + sz) - evt->offset; + evt->buffer = audioCache.open(af, initial_chunksize, + af->filechannel, evt->cache_id); + evt->buffer_size = initial_chunksize; + } + + { + MutexAutolock l(af->mutex); + + size_t n = 0; // default start point is 0. + + // If we are not at offset 0 in current buffer: + if(evt->offset > (size_t)pos) + { + n = evt->offset - pos; + } + + size_t end = sz; // default end point is the end of the buffer. + + // Find the end point intra-buffer + if((evt->t + end - n) > af->size) + { + end = af->size - evt->t + n; + } + + // This should not be necessary but make absolutely sure that we do + // not write over the end of the buffer. + if(end > sz) + { + end = sz; + } + + size_t t = 0; // Internal buffer counter + if(evt->rampdown == NO_RAMPDOWN) + { + #ifdef SSE -// DEBUG(drumgizmo,"%d\n", evt->t); fflush(stdout); - size_t optend = ((end - n) / N) * N + n; - for(; n < optend; n += N) { - *(vNsf*)&(s[n]) += *(vNsf*)&(af->data[evt->t]); - evt->t += N; - } + size_t optend = ((end - n) / N) * N + n; + + // Force source addr to be 16 byte aligned... + // (might skip 1 or 2 samples) + while((size_t)&evt->buffer[t] % 16) + { + ++t; + } + + for(; (n < optend) && (t < evt->buffer_size); n += N) + { + *(vNsf*)&(s[n]) += *(vNsf*)&(evt->buffer[t]); + t += N; + } #endif - for(; n < end; n++) { - s[n] += af->data[evt->t]; - evt->t++; - } - } else { // Ramp down in progress. - for(; n < end && evt->rampdown; n++) { - float scale = (float)evt->rampdown/(float)evt->ramp_start; - s[n] += af->data[evt->t] * scale; - evt->t++; - evt->rampdown--; - } - - if(evt->rampdown == 0) { - removeevent = true; // Down ramp done. Remove event. - } - } - - if(evt->t >= af->size) { - removeevent = true; - } - - } - } - break; - } - - if(removeevent) { - delete event; - i = activeevents[ch].erase(i); - continue; - } - i++; - } + for(; (n < end) && (t < evt->buffer_size); ++n) + { + s[n] += evt->buffer[t]; + ++t; + } + } + else + { // Ramp down in progress. + for(; (n < end) && (t < evt->buffer_size) && evt->rampdown; ++n) + { + float scale = (float)evt->rampdown/(float)evt->ramp_start; + s[n] += evt->buffer[t] * scale; + ++t; + evt->rampdown--; + } + } + + // Add internal buffer counter to "global" event counter. + evt->t += evt->buffer_size; + + if((evt->t < af->size) && (evt->rampdown != 0)) + { + evt->buffer = audioCache.next(evt->cache_id, evt->buffer_size); + } + else + { + removeevent = true; + } + + if(removeevent) + { + audioCache.close(evt->cache_id); + } + } + } + break; + } + + if(removeevent) + { + delete event; + i = activeevents[ch].erase(i); + continue; + } + } } void DrumGizmo::stop() { - // engine.stop(); + // engine.stop(); } int DrumGizmo::samplerate() { - return Conf::samplerate; + return Conf::samplerate; } void DrumGizmo::setSamplerate(int samplerate) { - Conf::samplerate = samplerate; + DEBUG(dgeditor, "%s samplerate: %d\n", __PRETTY_FUNCTION__, samplerate); + Conf::samplerate = samplerate; #ifdef WITH_RESAMPLER - for(int i = 0; i < MAX_NUM_CHANNELS; i++) { - resampler[i].setup(kit.samplerate(), Conf::samplerate); - } + for(int i = 0; i < MAX_NUM_CHANNELS; ++i) + { + resampler[i].setup(kit.samplerate(), Conf::samplerate); + } + if(resampler[0].ratio() != 1) + { + setFrameSize(RESAMPLER_INPUT_BUFFER); + } #endif/*WITH_RESAMPLER*/ - } std::string float2str(float a) { - char buf[256]; - snprintf_nol(buf, sizeof(buf) - 1, "%f", a); - return buf; + char buf[256]; + snprintf_nol(buf, sizeof(buf) - 1, "%f", a); + return buf; } std::string bool2str(bool a) { - return a?"true":"false"; + return a?"true":"false"; } float str2float(std::string a) { - if(a == "") return 0.0; - return atof_nol(a.c_str()); + if(a == "") + { + return 0.0; + } + + return atof_nol(a.c_str()); } std::string DrumGizmo::configString() { - std::string mmapfile; - if(ie->isMidiEngine()) { - AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; - mmapfile = aim->midimapFile(); - } - - return - "<config>\n" - " <value name=\"drumkitfile\">" + kit.file() + "</value>\n" - " <value name=\"midimapfile\">" + mmapfile + "</value>\n" - " <value name=\"enable_velocity_modifier\">" + - bool2str(Conf::enable_velocity_modifier) + "</value>\n" - " <value name=\"velocity_modifier_falloff\">" + - float2str(Conf::velocity_modifier_falloff) + "</value>\n" - " <value name=\"velocity_modifier_weight\">" + - float2str(Conf::velocity_modifier_weight) + "</value>\n" - " <value name=\"enable_velocity_randomiser\">" + - bool2str(Conf::enable_velocity_randomiser) + "</value>\n" - " <value name=\"velocity_randomiser_weight\">" + - float2str(Conf::velocity_randomiser_weight) + "</value>\n" - "</config>"; + std::string mmapfile; + if(ie->isMidiEngine()) + { + AudioInputEngineMidi *aim = (AudioInputEngineMidi*)ie; + mmapfile = aim->midimapFile(); + } + + return + "<config>\n" + " <value name=\"drumkitfile\">" + kit.file() + "</value>\n" + " <value name=\"midimapfile\">" + mmapfile + "</value>\n" + " <value name=\"enable_velocity_modifier\">" + + bool2str(Conf::enable_velocity_modifier) + "</value>\n" + " <value name=\"velocity_modifier_falloff\">" + + float2str(Conf::velocity_modifier_falloff) + "</value>\n" + " <value name=\"velocity_modifier_weight\">" + + float2str(Conf::velocity_modifier_weight) + "</value>\n" + " <value name=\"enable_velocity_randomiser\">" + + bool2str(Conf::enable_velocity_randomiser) + "</value>\n" + " <value name=\"velocity_randomiser_weight\">" + + float2str(Conf::velocity_randomiser_weight) + "</value>\n" + "</config>"; } - bool DrumGizmo::setConfigString(std::string cfg) { - DEBUG(config, "Load config: %s\n", cfg.c_str()); - - std::string dkf; - ConfigParser p; - if(p.parse(cfg)) { - ERR(drumgizmo, "Config parse error.\n"); - return false; - } - - if(p.value("enable_velocity_modifier") != "") { - Conf::enable_velocity_modifier = - p.value("enable_velocity_modifier") == "true"; - } - - if(p.value("velocity_modifier_falloff") != "") { - Conf::velocity_modifier_falloff = - str2float(p.value("velocity_modifier_falloff")); - } - - if(p.value("velocity_modifier_weight") != "") { - Conf::velocity_modifier_weight = - str2float(p.value("velocity_modifier_weight")); - } - - if(p.value("enable_velocity_randomiser") != "") { - Conf::enable_velocity_randomiser = - p.value("enable_velocity_randomiser") == "true"; - } - - if(p.value("velocity_randomiser_weight") != "") { - Conf::velocity_randomiser_weight = - str2float(p.value("velocity_randomiser_weight")); - } - - if(p.value("enable_resampling") != "") { - Conf::enable_resampling = - p.value("enable_resampling") == "true"; - } - - std::string newkit = p.value("drumkitfile"); - if(newkit != "" && kit.file() != newkit) { - /* - if(!loadkit(p.values["drumkitfile"])) return false; - init(true); - */ - LoadDrumKitMessage *msg = new LoadDrumKitMessage(); - msg->drumkitfile = newkit; - msghandler.sendMessage(MSGRCV_ENGINE, msg); - } - - std::string newmidimap = p.value("midimapfile"); - if(newmidimap != "") { - //midimapfile = newmidimap; - LoadMidimapMessage *msg = new LoadMidimapMessage(); - msg->midimapfile = newmidimap; - msghandler.sendMessage(MSGRCV_ENGINE, msg); - } - - return true; -} - -#ifdef TEST_DRUMGIZMO -//deps: instrument.cc sample.cc channel.cc audiofile.cc drumkit.cc drumkitparser.cc configuration.cc saxparser.cc instrumentparser.cc path.cc -//cflags: $(SNDFILE_CFLAGS) $(EXPAT_CFLAGS) -I../include -DSSE -msse -msse2 -msse3 -//libs: $(SNDFILE_LIBS) $(EXPAT_LIBS) -#include "test.h" - -static float f(size_t x) -{ - return x + 1.0; + DEBUG(config, "Load config: %s\n", cfg.c_str()); + + std::string dkf; + ConfigParser p; + if(p.parse(cfg)) + { + ERR(drumgizmo, "Config parse error.\n"); + return false; + } + + if(p.value("enable_velocity_modifier") != "") + { + Conf::enable_velocity_modifier = + p.value("enable_velocity_modifier") == "true"; + } + + if(p.value("velocity_modifier_falloff") != "") + { + Conf::velocity_modifier_falloff = + str2float(p.value("velocity_modifier_falloff")); + } + + if(p.value("velocity_modifier_weight") != "") + { + Conf::velocity_modifier_weight = + str2float(p.value("velocity_modifier_weight")); + } + + if(p.value("enable_velocity_randomiser") != "") + { + Conf::enable_velocity_randomiser = + p.value("enable_velocity_randomiser") == "true"; + } + + if(p.value("velocity_randomiser_weight") != "") + { + Conf::velocity_randomiser_weight = + str2float(p.value("velocity_randomiser_weight")); + } + + if(p.value("enable_resampling") != "") + { + Conf::enable_resampling = + p.value("enable_resampling") == "true"; + } + + std::string newkit = p.value("drumkitfile"); + if(newkit != "" && kit.file() != newkit) + { + /* + if(!loadkit(p.values["drumkitfile"])) + { + return false; + } + init(true); + */ + LoadDrumKitMessage *msg = new LoadDrumKitMessage(); + msg->drumkitfile = newkit; + msghandler.sendMessage(MSGRCV_ENGINE, msg); + } + + std::string newmidimap = p.value("midimapfile"); + if(newmidimap != "") + { + //midimapfile = newmidimap; + LoadMidimapMessage *msg = new LoadMidimapMessage(); + msg->midimapfile = newmidimap; + msghandler.sendMessage(MSGRCV_ENGINE, msg); + } + + return true; } - -class AITest : public AudioInputEngine { -public: - bool init(Instruments &instruments) { return true; } - void setParm(std::string parm, std::string value) {} - bool start() { return true; } - void stop() {} - void pre() {} - event_t *run(size_t pos, size_t len, size_t *nevents) - { - event_t *e = NULL; - *nevents = 0; - - if(pos <= offset && offset < pos + len) { - e = new event_t; - - e->type = TYPE_ONSET; - e->instrument = 0; - e->velocity = 1.0; - e->offset = offset - pos; - - *nevents = 1; - } - return e; - } - void post() {} - size_t offset; -}; - -class AOTest : public AudioOutputEngine { -public: - bool init(Channels channels) { return true; } - void setParm(std::string parm, std::string value) {} - bool start() { return true; } - void stop() {} - void pre(size_t nsamples) {} - void run(int ch, sample_t *samples, size_t nsamples) - { - } - void post(size_t nsamples) {} -}; - -const char xml_kit[] = - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<drumkit name=\"test\" description=\"\">\n" - " <channels>\n" - " <channel name=\"ch1\"/>\n" - " </channels>\n" - " <instruments>\n" - " <instrument name=\"instr1\" file=\"instr1.xml\">\n" - " <channelmap in=\"ch1\" out=\"ch1\"/>\n" - " </instrument>\n" - " </instruments>\n" - "</drumkit>"; - -const char xml_instr[] = - "<?xml version='1.0' encoding='UTF-8'?>\n" - "<instrument name=\"instr1\">\n" - " <samples>\n" - " <sample name=\"sample1\">\n" - " <audiofile channel=\"ch1\" file=\"instr1.wav\"/>\n" - " </sample>\n" - " </samples>\n" - " <velocities>\n" - " <velocity lower=\"0\" upper=\"1.0\">\n" - " <sampleref name=\"sample1\"/>\n" - " </velocity>\n" - " </velocities>\n" - "</instrument>"; - -#define PCM_SIZE 100 - -void createTestKit() -{ - FILE *fp; - fp = fopen("/tmp/kit.xml", "w"); - fwrite(xml_kit, strlen(xml_kit), 1, fp); - fclose(fp); - - fp = fopen("/tmp/instr1.xml", "w"); - fwrite(xml_instr, strlen(xml_instr), 1, fp); - fclose(fp); - - SF_INFO sf_info; - sf_info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; - sf_info.samplerate = 44100; - sf_info.channels = 1; - - SNDFILE *fh = sf_open("/tmp/instr1.wav", SFM_WRITE, &sf_info); - if(!fh) { - printf("Error: %s\n", sf_strerror(fh)); - } - - size_t size = PCM_SIZE; - sample_t samples[size]; - - for(size_t i = 0; i < size; i++) { - samples[i] = f(i);//(float)i / (float)size; - } - - sf_write_float(fh, samples, size); - sf_close(fh); -} - -void deleteTestKit() -{ - unlink("/tmp/kit.xml"); - unlink("/tmp/instr1.xml"); - unlink("/tmp/instr1.wav"); -} - -TEST_BEGIN; - -createTestKit(); - -size_t size = PCM_SIZE; -//for(size_t chunksz = 1; chunksz < size + 1; chunksz++) { -size_t chunksz = 16; { - sample_t samples[chunksz]; - - for(size_t offset = 0; offset < chunksz + size + 1; offset++) { - //size_t offset = 5; { - for(size_t padding = 0; padding < chunksz + size + offset + 1; padding++) { - //size_t padding = 2; { - TEST_MSG("Values (offset %d, padding %d, chunksz %d)", - offset, padding, chunksz); - - AOTest ao; - AITest ai; ai.offset = offset; - DrumGizmo dg(&ao, &ai); - dg.loadkit("/tmp/kit.xml"); - - size_t pos = 0; - // sample_t samples[chunksz]; - while(pos < offset + size + padding) { - dg.run(pos, samples, chunksz); - - float err = 0; - size_t errcnt = 0; - for(size_t i = 0; i < chunksz && pos < offset + size + padding; i++) { - float val = 0.0; - if(pos >= offset && pos < (offset + size)) val = f(pos - offset); - float diff = samples[i] - val; - /* - if(diff != 0.0) { - TEST_EQUAL_FLOAT(samples[i], val, - "samples[%d] ?= val, pos %d", i, pos); - } - */ - if(diff != 0.0) errcnt++; - - err += fabs(diff); - pos++; - } - - TEST_EQUAL_FLOAT(err, 0.0, - "Compare error (offset %d, padding %d, chunksz %d)", - offset, padding, chunksz); - TEST_EQUAL_INT(errcnt, 0, - "Compare count (offset %d, padding %d, chunksz %d)", - offset, padding, chunksz); - } - - } - } -} - -deleteTestKit(); - -TEST_END; - -#endif/*TEST_DRUMGIZMO*/ diff --git a/src/drumgizmo.h b/src/drumgizmo.h index 5e58ba5..2778092 100644 --- a/src/drumgizmo.h +++ b/src/drumgizmo.h @@ -24,8 +24,7 @@ * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ -#ifndef __DRUMGIZMO_DRUMGIZMO_H__ -#define __DRUMGIZMO_DRUMGIZMO_H__ +#pragma once #include <string> #include <list> @@ -38,6 +37,7 @@ #include "drumkit.h" #include "drumkitloader.h" +#include "audiocache.h" #include "mutex.h" @@ -51,52 +51,57 @@ #define MAX_NUM_CHANNELS 64 #define REFSFILE "refs.conf" +#define RESAMPLER_INPUT_BUFFER 64 -class DrumGizmo : public MessageReceiver { +class DrumGizmo + : public MessageReceiver +{ public: - DrumGizmo(AudioOutputEngine *outputengine, AudioInputEngine *inputengine); - virtual ~DrumGizmo(); + DrumGizmo(AudioOutputEngine *outputengine, AudioInputEngine *inputengine); + virtual ~DrumGizmo(); - bool loadkit(std::string kitfile); + bool loadkit(std::string kitfile); - bool init(); + bool init(); - /** - * @param endpos number of samples to process, -1 := never stop. - */ - void run(int endpos); - bool run(size_t pos, sample_t *samples, size_t nsamples); - void stop(); + void run(int endpos); + bool run(size_t pos, sample_t *samples, size_t nsamples); + void stop(); - void getSamples(int ch, int pos, sample_t *s, size_t sz); + void getSamples(int ch, int pos, sample_t *s, size_t sz); - std::string configString(); - bool setConfigString(std::string cfg); + std::string configString(); + bool setConfigString(std::string cfg); - void handleMessage(Message *msg); + void handleMessage(Message *msg); - int samplerate(); - void setSamplerate(int samplerate); + int samplerate(); + void setSamplerate(int samplerate); -private: - DrumKitLoader loader; + void setFrameSize(size_t framesize); - Mutex mutex; - bool is_stopping; ///< Is set to true when a TYPE_STOP event has been seen. + void setFreeWheel(bool freewheel); - AudioOutputEngine *oe; - AudioInputEngine *ie; +protected: + DrumKitLoader loader; - std::list< Event* > activeevents[MAX_NUM_CHANNELS]; + Mutex mutex; + bool is_stopping; ///< Is set to true when a TYPE_STOP event has been seen. - CHResampler resampler[MAX_NUM_CHANNELS]; - sample_t resampler_output_buffer[MAX_NUM_CHANNELS][4096]; - sample_t resampler_input_buffer[MAX_NUM_CHANNELS][64]; + AudioOutputEngine *oe; + AudioInputEngine *ie; - std::map<std::string, AudioFile *> audiofiles; + std::list< Event* > activeevents[MAX_NUM_CHANNELS]; - DrumKit kit; -}; + CHResampler resampler[MAX_NUM_CHANNELS]; + sample_t resampler_output_buffer[MAX_NUM_CHANNELS][4096]; + sample_t resampler_input_buffer[MAX_NUM_CHANNELS][RESAMPLER_INPUT_BUFFER]; + + std::map<std::string, AudioFile *> audiofiles; + AudioCache audioCache; + DrumKit kit; -#endif/*__DRUMGIZMO_DRUMGIZMO_H__*/ + size_t framesize; + bool freewheel; +}; diff --git a/src/drumkitloader.cc b/src/drumkitloader.cc index bf01db6..64d6710 100644 --- a/src/drumkitloader.cc +++ b/src/drumkitloader.cc @@ -32,123 +32,165 @@ #include "drumgizmo.h" DrumKitLoader::DrumKitLoader() - : semaphore("drumkitloader") + : semaphore("drumkitloader") + , framesize(0) { - run(); - run_semaphore.wait(); // Wait for the thread to actually start. + run(); + run_semaphore.wait(); // Wait for the thread to actually start. } DrumKitLoader::~DrumKitLoader() { - if(running) { - stop(); - } + DEBUG(loader, "~DrumKitLoader() pre\n"); + + if(running) + { + framesize_semaphore.post(); + stop(); + } + + DEBUG(loader, "~DrumKitLoader() post\n"); } void DrumKitLoader::stop() { - { - MutexAutolock l(mutex); - load_queue.clear(); - } - - running = false; - semaphore.post(); - wait_stop(); + { + MutexAutolock l(mutex); + load_queue.clear(); + } + + running = false; + semaphore.post(); + wait_stop(); } void DrumKitLoader::skip() { - MutexAutolock l(mutex); - load_queue.clear(); + MutexAutolock l(mutex); + load_queue.clear(); +} + +void DrumKitLoader::setFrameSize(size_t framesize) +{ + DEBUG(loader, "%s pre\n", __PRETTY_FUNCTION__); + + { + MutexAutolock l(mutex); + this->framesize = framesize; + framesize_semaphore.post(); // Signal that the framesize has been set. + } + + DEBUG(loader, "%s post\n", __PRETTY_FUNCTION__); } bool DrumKitLoader::isDone() { - MutexAutolock l(mutex); - return load_queue.size() == 0; + MutexAutolock l(mutex); + return load_queue.size() == 0; } void DrumKitLoader::loadKit(DrumKit *kit) { - MutexAutolock l(mutex); - - DEBUG(loader, "Create AudioFile queue from DrumKit\n"); - - total_num_audiofiles = 0;// For UI Progress Messages - - { // Count total number of files that need loading: - Instruments::iterator i = kit->instruments.begin(); - while(i != kit->instruments.end()) { - Instrument *instr = *i; - total_num_audiofiles += instr->audiofiles.size(); - i++; - } - } - - fraction = total_num_audiofiles / 200; - if(fraction == 0) fraction = 1; - - { // Now actually queue them for loading: - Instruments::iterator i = kit->instruments.begin(); - while(i != kit->instruments.end()) { - Instrument *instr = *i; - - std::vector<AudioFile*>::iterator af = instr->audiofiles.begin(); - while(af != instr->audiofiles.end()) { - AudioFile *audiofile = *af; - load_queue.push_back(audiofile); - af++; - } - - i++; - } - } - - loaded = 0; // For UI Progress Messages - - DEBUG(loader, "Queued %d (size: %d) AudioFiles for loading.\n", - (int)total_num_audiofiles, (int)load_queue.size()); - - semaphore.post(); // Start loader loop. + MutexAutolock l(mutex); + + DEBUG(loader, "Create AudioFile queue from DrumKit\n"); + + total_num_audiofiles = 0;// For UI Progress Messages + + { // Count total number of files that need loading: + Instruments::iterator i = kit->instruments.begin(); + while(i != kit->instruments.end()) + { + Instrument *instr = *i; + total_num_audiofiles += instr->audiofiles.size(); + ++i; + } + } + + fraction = total_num_audiofiles / 200; + if(fraction == 0) + { + fraction = 1; + } + + { // Now actually queue them for loading: + Instruments::iterator i = kit->instruments.begin(); + while(i != kit->instruments.end()) + { + Instrument *instr = *i; + + std::vector<AudioFile*>::iterator af = instr->audiofiles.begin(); + while(af != instr->audiofiles.end()) + { + AudioFile *audiofile = *af; + load_queue.push_back(audiofile); + af++; + } + + ++i; + } + } + + loaded = 0; // For UI Progress Messages + + DEBUG(loader, "Queued %d (size: %d) AudioFiles for loading.\n", + (int)total_num_audiofiles, (int)load_queue.size()); + + semaphore.post(); // Start loader loop. } void DrumKitLoader::thread_main() { - running = true; - - run_semaphore.post(); // Signal that the thread has been started. - - while(running) { - size_t size; - { - MutexAutolock l(mutex); - size = load_queue.size(); - } - - // Only sleep if queue is empty. - if(size == 0) semaphore.wait(); - - std::string filename; - { - MutexAutolock l(mutex); - if(load_queue.size() == 0) continue; - AudioFile *audiofile = load_queue.front(); - load_queue.pop_front(); - filename = audiofile->filename; - audiofile->load(); - } - - loaded++; - - if(loaded % fraction == 0 || loaded == total_num_audiofiles) { - LoadStatusMessage *ls = new LoadStatusMessage(); - ls->number_of_files = total_num_audiofiles; - ls->numer_of_files_loaded = loaded; - ls->current_file = filename; - msghandler.sendMessage(MSGRCV_UI, ls); - } - } - - DEBUG(loader, "Loader thread finished."); + running = true; + + run_semaphore.post(); // Signal that the thread has been started. + + framesize_semaphore.wait(); // Wait until the framesize has been set. + + while(running) + { + size_t size; + { + MutexAutolock l(mutex); + size = load_queue.size(); + } + + // Only sleep if queue is empty. + if(size == 0) + { + semaphore.wait(); + } + + std::string filename; + { + MutexAutolock l(mutex); + if(load_queue.size() == 0) + { + continue; + } + AudioFile *audiofile = load_queue.front(); + load_queue.pop_front(); + filename = audiofile->filename; + size_t preload_size = framesize * CHUNK_MULTIPLIER + framesize; + if(preload_size < 1024) + { + preload_size = 1024; + } + (void)preload_size; + audiofile->load(ALL_SAMPLES); // Note: Change this to enable diskstreaming + } + + loaded++; + + if(loaded % fraction == 0 || loaded == total_num_audiofiles) + { + LoadStatusMessage *ls = new LoadStatusMessage(); + ls->number_of_files = total_num_audiofiles; + ls->numer_of_files_loaded = loaded; + ls->current_file = filename; + msghandler.sendMessage(MSGRCV_UI, ls); + } + } + + DEBUG(loader, "Loader thread finished."); } diff --git a/src/drumkitloader.h b/src/drumkitloader.h index b4a0a69..0532691 100644 --- a/src/drumkitloader.h +++ b/src/drumkitloader.h @@ -24,8 +24,7 @@ * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ -#ifndef __DRUMGIZMO_DRUMKITLOADER_H__ -#define __DRUMGIZMO_DRUMKITLOADER_H__ +#pragma once #include <string> #include <list> @@ -36,64 +35,51 @@ #include "drumkit.h" -/** - * This class is responsible for loading the drumkits in its own thread. - * All interaction calls are simply modifying queues and not doing any - * work in-sync with the caller. - * This means that if loadKit(...) is called, one cannot assume that the - * drumkit has actually been loaded when the call returns. - */ -class DrumKitLoader : public Thread { +//! This class is responsible for loading the drumkits in its own thread. +//! All interaction calls are simply modifying queues and not doing any +//! work in-sync with the caller. +//! This means that if loadKit(...) is called, one cannot assume that the +//! drumkit has actually been loaded when the call returns. +class DrumKitLoader + : public Thread +{ public: - /** - * The constrcutor starts the loader thread. - */ - DrumKitLoader(); + //! The constrcutor starts the loader thread. + DrumKitLoader(); + + //! The destructor signals the thread to stop and waits to merge before + //! returning (ie. deleting the object will garantuee that the thread has + //! been stopped). + ~DrumKitLoader(); - /** - * The destructor signals the thread to stop and waits to merge before - * returning (ie. deleting the object will garantuee that the thread has - * been stopped). - */ - ~DrumKitLoader(); + //! Signal the loader to start loading all audio files contained in kit. + //! All other AudioFiles in queue will be removed before the new ones are + //! scheduled. + void loadKit(DrumKit *kit); - /** - * Signal the loader to start loading all audio files contained in kit. - * All other AudioFiles in queue will be removed before the new ones are - * scheduled. - */ - void loadKit(DrumKit *kit); - - // I have no idea what this does.. - //void reset(AudioFile* af); + void thread_main(); - void thread_main(); + //! Simply reports if the load queue is empty (i.e. all AudioFiles has been + //! loaded). + bool isDone(); - /** - * Simply reports if the load queue is empty (i.e. all AudioFiles has been - * loaded). - */ - bool isDone(); + //! Signal the loader to stop and wait until it has. + void stop(); - /** - * Signal the loader to stop and wait until it has. - */ - void stop(); + //! Skip all queued AudioFiles. + void skip(); - /** - * Skip all queued AudioFiles. - */ - void skip(); + void setFrameSize(size_t framesize); -private: - Semaphore run_semaphore; - Semaphore semaphore; - Mutex mutex; +protected: + Semaphore run_semaphore; + Semaphore semaphore; + Semaphore framesize_semaphore; + Mutex mutex; volatile bool running{false}; - std::list<AudioFile*> load_queue; + std::list<AudioFile*> load_queue; size_t total_num_audiofiles{0}; size_t fraction{1}; size_t loaded{0}; + size_t framesize{0}; }; - -#endif/*__DRUMGIZMO_DRUMKITLOADER_H__*/ diff --git a/src/events.h b/src/events.h index fa0147b..26eaf3f 100644 --- a/src/events.h +++ b/src/events.h @@ -35,6 +35,7 @@ #include "audiofile.h" #include "audio.h" #include "mutex.h" +#include "audiocache.h" typedef unsigned int timepos_t; @@ -58,6 +59,7 @@ public: EventSample(channel_t c, float g, AudioFile *af, std::string grp, void *instr) { + cache_id = CACHE_NOID; channel = c; gain = g; t = 0; @@ -70,6 +72,10 @@ public: Event::type_t type() { return Event::sample; } + cacheid_t cache_id; + sample_t *buffer; + size_t buffer_size; + float gain; unsigned int t; AudioFile *file; diff --git a/src/mutex.cc b/src/mutex.cc index 22d59a6..dfdab33 100644 --- a/src/mutex.cc +++ b/src/mutex.cc @@ -27,129 +27,93 @@ */ #include "mutex.h" +#include <hugin.hpp> + #ifdef WIN32 #include <windows.h> #else #include <pthread.h> +#include <errno.h> #endif struct mutex_private_t { #ifdef WIN32 - HANDLE mutex; + HANDLE mutex; #else - pthread_mutex_t mutex; + pthread_mutex_t mutex; #endif }; Mutex::Mutex() { - prv = new struct mutex_private_t(); + prv = new struct mutex_private_t(); #ifdef WIN32 - prv->mutex = CreateMutex(NULL, // default security attributes - FALSE, // initially not owned - NULL); // unnamed mutex + prv->mutex = CreateMutex(nullptr, // default security attributes + FALSE, // initially not owned + nullptr); // unnamed mutex #else - pthread_mutex_init (&prv->mutex, NULL); + pthread_mutex_init (&prv->mutex, nullptr); #endif } Mutex::~Mutex() { #ifdef WIN32 - CloseHandle(prv->mutex); + CloseHandle(prv->mutex); #else - pthread_mutex_destroy(&prv->mutex); + pthread_mutex_destroy(&prv->mutex); #endif - if(prv) delete prv; + if(prv) + { + delete prv; + } +} + +//! \return true if the function succeeds in locking the mutex for the thread. +//! false otherwise. +bool Mutex::try_lock() +{ +#ifdef WIN32 + DEBUG(mutex, "%s\n", __PRETTY_FUNCTION__); + + DWORD result = WaitForSingleObject(prv->mutex, 0); + + DEBUG(mutex, "WAIT_OBJECT_0: %lu, WAIT_TIMEOUT: %lu, result: %lu\n", + WAIT_OBJECT_0, WAIT_TIMEOUT, result); + + return result != WAIT_TIMEOUT; +#else + return pthread_mutex_trylock(&prv->mutex) != EBUSY; +#endif } void Mutex::lock() { #ifdef WIN32 - WaitForSingleObject(prv->mutex, // handle to mutex - INFINITE); // no time-out interval + WaitForSingleObject(prv->mutex, // handle to mutex + INFINITE); // no time-out interval #else - pthread_mutex_lock(&prv->mutex); + pthread_mutex_lock(&prv->mutex); #endif } void Mutex::unlock() { #ifdef WIN32 - ReleaseMutex(prv->mutex); + ReleaseMutex(prv->mutex); #else - pthread_mutex_unlock(&prv->mutex); + pthread_mutex_unlock(&prv->mutex); #endif } MutexAutolock::MutexAutolock(Mutex &m) - : mutex(m) + : mutex(m) { - mutex.lock(); + mutex.lock(); } MutexAutolock::~MutexAutolock() { - mutex.unlock(); -} - -#ifdef TEST_MUTEX -//deps: -//cflags: $(PTHREAD_CFLAGS) -//libs: $(PTHREAD_LIBS) -#include <test.h> - -#include <unistd.h> - -volatile int cnt = 0; - -static void* thread_run(void *data) -{ - Mutex *mutex = (Mutex*)data; - mutex->lock(); - cnt++; - mutex->unlock(); - return NULL; + mutex.unlock(); } - -TEST_BEGIN; - -Mutex mutex; - -mutex.lock(); -TEST_FALSE(mutex.trylock(), "Testing if trylock works negative."); -mutex.unlock(); -TEST_TRUE(mutex.trylock(), "Testing if trylock works positive."); -mutex.unlock(); - -mutex.lock(); - -pthread_attr_t attr; -pthread_t tid; -pthread_attr_init(&attr); -pthread_create(&tid, &attr, thread_run, &mutex); - -sleep(1); -TEST_EQUAL_INT(cnt, 0, "Testing if lock prevent cnt from increasing."); -mutex.unlock(); - -sleep(1); -TEST_EQUAL_INT(cnt, 1, "Testing if unlock makes cnt increase."); - -pthread_join(tid, NULL); -pthread_attr_destroy(&attr); - -{ - TEST_TRUE(mutex.trylock(), "Testing if autolock has not yet locked the mutex."); - mutex.unlock(); - MutexAutolock mlock(mutex); - TEST_FALSE(mutex.trylock(), "Testing if autolock worked."); -} - -TEST_TRUE(mutex.trylock(), "Testing if autolock has released the lock on the mutex."); -mutex.unlock(); - -TEST_END; - -#endif/*TEST_MUTEX*/ diff --git a/src/mutex.h b/src/mutex.h index 11704d4..4659a07 100644 --- a/src/mutex.h +++ b/src/mutex.h @@ -25,31 +25,35 @@ * along with Pracro; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ -#ifndef __PRACRO_MUTEX_H__ -#define __PRACRO_MUTEX_H__ +#pragma once struct mutex_private_t; class Mutex { public: - Mutex(); - ~Mutex(); + Mutex(); + ~Mutex(); - bool trylock(); - void lock(); - void unlock(); + bool try_lock(); + void lock(); + void unlock(); private: - struct mutex_private_t* prv; + struct mutex_private_t* prv; }; +#ifdef WIN32 +// Hack: mingw doesn't have std::mutex +namespace std { + class mutex : public Mutex {}; +} +#endif + class MutexAutolock { public: - MutexAutolock(Mutex &mutex); - ~MutexAutolock(); + MutexAutolock(Mutex &mutex); + ~MutexAutolock(); private: - Mutex &mutex; + Mutex &mutex; }; - -#endif/*__PRACRO_MUTEX_H__*/ diff --git a/src/semaphore.cc b/src/semaphore.cc index 3f5781f..2bd244c 100644 --- a/src/semaphore.cc +++ b/src/semaphore.cc @@ -48,7 +48,7 @@ struct semaphore_private_t { Semaphore::Semaphore(const char *name) { this->name = name; - DEBUG(semaphore, "Create [%s]\n", name); + // DEBUG(semaphore, "Create [%s]\n", name); prv = new struct semaphore_private_t(); @@ -64,7 +64,7 @@ Semaphore::Semaphore(const char *name) Semaphore::~Semaphore() { - DEBUG(semaphore, "Delete [%s]\n", name); + // DEBUG(semaphore, "Delete [%s]\n", name); #ifdef WIN32 CloseHandle(prv->semaphore); @@ -77,7 +77,7 @@ Semaphore::~Semaphore() void Semaphore::post() { - DEBUG(semaphore, "Post [%s]\n", name); + // DEBUG(semaphore, "Post [%s]\n", name); #ifdef WIN32 ReleaseSemaphore(prv->semaphore, 1, NULL); @@ -88,7 +88,7 @@ void Semaphore::post() void Semaphore::wait() { - DEBUG(semaphore, "Wait [%s]\n", name); + // DEBUG(semaphore, "Wait [%s]\n", name); #ifdef WIN32 WaitForSingleObject(prv->semaphore, INFINITE); |