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);  | 
