diff options
39 files changed, 3538 insertions, 1498 deletions
@@ -2,9 +2,10 @@ Makefile Makefile.in aclocal.m4 autom4te.cache/ +compile config.guess config.h -config.h.in +config.h.in* config.log config.status config.sub @@ -37,3 +38,8 @@ test/engine test/gui test/lv2 test/resource +drumgizmo-*.tar.gz +tst +vst/Makefile.mingw32 +test/*.log +test/*.trs diff --git a/drumgizmo/drumgizmoc.cc b/drumgizmo/drumgizmoc.cc index 1ccc151..10dcda5 100644 --- a/drumgizmo/drumgizmoc.cc +++ b/drumgizmo/drumgizmoc.cc @@ -328,6 +328,8 @@ int CliMain::run(int argc, char *argv[]) DrumGizmo gizmo(oe, ie); + gizmo.setFrameSize(oe->getBufferSize()); + if(kitfile == "" || !gizmo.loadkit(kitfile)) { printf("Failed to load \"%s\".\n", kitfile.c_str()); return 1; diff --git a/lv2/drumgizmo.ttl b/lv2/drumgizmo.ttl index 23345f3..6ce3e91 100644 --- a/lv2/drumgizmo.ttl +++ b/lv2/drumgizmo.ttl @@ -37,108 +37,119 @@ lv2:optionalFeature <http://lv2plug.in/ns/ext/uri-map> ; lv2:optionalFeature <http://lv2plug.in/ns/ext/event> ; lv2:extensionData state:interface ; -lv2:port [ + lv2:port [ + a lv2:InputPort, lv2:ControlPort ; + lv2:index 0 ; + lv2:symbol "lv2_freewheel" ; + lv2:name "Freewheel" ; + lv2:default 0.0 ; + lv2:minimum 0.0 ; + lv2:maximum 1.0 ; + lv2:designation <http://lv2plug.in/ns/lv2core#freeWheeling> ; + lv2:portProperty lv2:toggled ; + lv2:portProperty epp:hasStrictBounds; + ] , [ a atom:AtomPort , lv2:InputPort; atom:bufferType atom:Sequence ; atom:supports <http://lv2plug.in/ns/ext/midi#MidiEvent> ; - lv2:index 0 ; + lv2:index 1 ; lv2:symbol "control" ; lv2:name "Control" ] , [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 1 ; + lv2:index 2 ; lv2:symbol "out1" ; lv2:name "Out1" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 2 ; + lv2:index 3 ; lv2:symbol "out2" ; lv2:name "Out2" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 3 ; + lv2:index 4 ; lv2:symbol "out3" ; lv2:name "Out3" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 4 ; + lv2:index 5 ; lv2:symbol "out4" ; lv2:name "Out4" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 5 ; + lv2:index 6 ; lv2:symbol "out5" ; lv2:name "Out5" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 6 ; + lv2:index 7 ; lv2:symbol "out6" ; lv2:name "Out6" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 7 ; + lv2:index 8 ; lv2:symbol "out7" ; lv2:name "Out7" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 8 ; + lv2:index 9 ; lv2:symbol "out8" ; lv2:name "Out8" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 9 ; + lv2:index 10 ; lv2:symbol "out9" ; lv2:name "Out9" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 10 ; + lv2:index 11 ; lv2:symbol "out10" ; lv2:name "Out10" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 11 ; + lv2:index 12 ; lv2:symbol "out11" ; lv2:name "Out11" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 12 ; + lv2:index 13 ; lv2:symbol "out12" ; lv2:name "Out12" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 13 ; + lv2:index 14 ; lv2:symbol "out13" ; lv2:name "Out13" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 14 ; + lv2:index 15 ; lv2:symbol "out14" ; lv2:name "Out14" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 15 ; + lv2:index 16 ; lv2:symbol "out15" ; lv2:name "Out15" ], [ a lv2:AudioPort , lv2:OutputPort ; - lv2:index 16 ; + lv2:index 17 ; lv2:symbol "out16" ; lv2:name "Out16" ] . diff --git a/lv2/input_lv2.cc b/lv2/input_lv2.cc index e70d293..f65cf83 100644 --- a/lv2/input_lv2.cc +++ b/lv2/input_lv2.cc @@ -86,7 +86,7 @@ event_t *InputLV2::run(size_t pos, size_t len, size_t *nevents) if ((data[0] & 0xF0) == 0x80) { // note off int key = data[1]; - + (void)key; DEBUG(lv2input, "Event (off) key:%d\n", key); } @@ -35,6 +35,12 @@ #include <hugin.hpp> +enum { + FREE_WHEEL_PORT = 0, + MIDI_PORT, + AUDIO_PORT_BASE +}; + #define DRUMGIZMO_URI "http://drumgizmo.org/lv2" #define NS_DG DRUMGIZMO_URI "/atom#" @@ -47,12 +53,11 @@ static DrumGizmo *dg_get_pci(LV2_Handle instance) return dglv2->dg; } -LV2_State_Status -dg_save(LV2_Handle instance, - LV2_State_Store_Function store, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature *const * features) +LV2_State_Status dg_save(LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature *const * features) { DGLV2 *dglv2 = (DGLV2 *)instance; @@ -77,12 +82,11 @@ dg_save(LV2_Handle instance, return LV2_STATE_SUCCESS; } -LV2_State_Status -dg_restore(LV2_Handle instance, - LV2_State_Retrieve_Function retrieve, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature *const * features) +LV2_State_Status dg_restore(LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature *const * features) { DGLV2 *dglv2 = (DGLV2 *)instance; @@ -127,6 +131,9 @@ LV2_Handle instantiate(const struct _LV2_Descriptor *descriptor, { DGLV2 *dglv2 = new DGLV2; + dglv2->free_wheel_port = NULL; // Not assigned + dglv2->pos = 0; // Start from the beginning + dglv2->map = NULL; for (int i = 0 ; features[i] ; i++) { if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { @@ -148,19 +155,22 @@ LV2_Handle instantiate(const struct _LV2_Descriptor *descriptor, return (LV2_Handle)dglv2; } -void connect_port(LV2_Handle instance, - uint32_t port, - void *data_location) +void connect_port(LV2_Handle instance, uint32_t port, void *data_location) { DGLV2 *dglv2 = (DGLV2 *)instance; - if(port == 0) {// MIDI in + if(port == FREE_WHEEL_PORT) { + dglv2->free_wheel_port = (float*)data_location; + } + + if(port == MIDI_PORT) { // MIDI in dglv2->in->eventPort = (LV2_Atom_Sequence*)data_location; - } else {// Audio Port - if(port - 1 < NUM_OUTPUTS) { - dglv2->out->outputPorts[port - 1].samples = (sample_t*)data_location; - dglv2->out->outputPorts[port - 1].size = 0; - } + } + + if(port >= AUDIO_PORT_BASE) { // Audio Port + uint32_t audio_port = port - AUDIO_PORT_BASE; + dglv2->out->outputPorts[audio_port].samples = (sample_t*)data_location; + dglv2->out->outputPorts[audio_port].size = 0; } } @@ -171,20 +181,25 @@ void activate(LV2_Handle instance) (void)dglv2; } -void run(LV2_Handle instance, - uint32_t sample_count) +void run(LV2_Handle instance, uint32_t sample_count) { - static size_t pos = 0; DGLV2 *dglv2 = (DGLV2 *)instance; - dglv2->dg->run(pos, dglv2->buffer, sample_count); + if(dglv2->free_wheel_port) { + dglv2->dg->setFreeWheel(*dglv2->free_wheel_port); + } + + if(dglv2->buffer_size != sample_count) { + dglv2->buffer_size = sample_count; + dglv2->dg->setFrameSize(sample_count); + } + dglv2->dg->run(dglv2->pos, dglv2->buffer, sample_count); - pos += sample_count; + dglv2->pos += sample_count; } void deactivate(LV2_Handle instance) { - // We don't really need to do anything here. DGLV2 *dglv2 = (DGLV2 *)instance; dglv2->dg->stop(); } diff --git a/lv2/lv2_instance.h b/lv2/lv2_instance.h index e050e22..0ce98bb 100644 --- a/lv2/lv2_instance.h +++ b/lv2/lv2_instance.h @@ -43,6 +43,8 @@ typedef struct { sample_t *buffer; size_t buffer_size; LV2_URID_Map* map; + float* free_wheel_port; + size_t pos; } DGLV2; #endif/*__DRUMGIZMO_LV2_INSTANCE_H__*/ 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); diff --git a/test/Makefile.am b/test/Makefile.am index 29ab4b1..8a746a6 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,7 +1,8 @@ # Rules for the test code (use `make check` to execute) include $(top_srcdir)/src/Makefile.am.drumgizmo -TESTS = resource engine gui resampler lv2 configfile +TESTS = resource engine gui resampler lv2 configfile audiocache \ + audiocachefile audiocacheidmanager audiocacheeventhandler check_PROGRAMS = $(TESTS) @@ -15,9 +16,65 @@ resource_SOURCES = \ test.cc \ resource_test.cc +audiocache_CXXFLAGS = -DOUTPUT=\"audiocache\" $(CPPUNIT_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/include \ + -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS) +audiocache_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS) +audiocache_SOURCES = \ + $(top_srcdir)/src/audiocache.cc \ + $(top_srcdir)/src/audiocacheeventhandler.cc \ + $(top_srcdir)/src/audiocachefile.cc \ + $(top_srcdir)/src/audiocacheidmanager.cc \ + $(top_srcdir)/src/thread.cc \ + $(top_srcdir)/src/mutex.cc \ + $(top_srcdir)/src/semaphore.cc \ + $(top_srcdir)/src/configuration.cc \ + $(top_srcdir)/src/audiofile.cc \ + test.cc \ + audiocachetest.cc + +audiocachefile_CXXFLAGS = -DOUTPUT=\"audiocachefile\" $(CPPUNIT_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/include \ + -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS) +audiocachefile_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS) +audiocachefile_SOURCES = \ + $(top_srcdir)/src/audiocachefile.cc \ + $(top_srcdir)/src/thread.cc \ + $(top_srcdir)/src/mutex.cc \ + $(top_srcdir)/src/semaphore.cc \ + $(top_srcdir)/src/configuration.cc \ + $(top_srcdir)/src/audiofile.cc \ + test.cc \ + audiocachefiletest.cc + +audiocacheidmanager_CXXFLAGS = -DOUTPUT=\"audiocacheidmanager\" \ + $(CPPUNIT_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/include \ + -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(SNDFILE_CFLAGS) +audiocacheidmanager_LDFLAGS = $(CPPUNIT_LIBS) $(SNDFILE_LIBS) +audiocacheidmanager_SOURCES = \ + $(top_srcdir)/src/audiocacheidmanager.cc \ + test.cc \ + audiocacheidmanagertest.cc + +audiocacheeventhandler_CXXFLAGS = -DOUTPUT=\"audiocacheeventhandler\" \ + $(CPPUNIT_CFLAGS) \ + -I$(top_srcdir)/src -I$(top_srcdir)/include \ + -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) $(SNDFILE_CFLAGS) +audiocacheeventhandler_LDFLAGS = $(PTHREAD_LIBS) $(CPPUNIT_LIBS) $(SNDFILE_LIBS) +audiocacheeventhandler_SOURCES = \ + $(top_srcdir)/src/audiocacheeventhandler.cc \ + $(top_srcdir)/src/audiocacheidmanager.cc \ + $(top_srcdir)/src/audiocachefile.cc \ + $(top_srcdir)/src/mutex.cc \ + $(top_srcdir)/src/thread.cc \ + $(top_srcdir)/src/semaphore.cc \ + test.cc \ + audiocacheeventhandlertest.cc + engine_CXXFLAGS = -DOUTPUT=\"engine\" $(CPPUNIT_CFLAGS) \ -I$(top_srcdir)/src -I$(top_srcdir)/include \ - -I$(top_srcdir)/hugin -DDISABLE_HUGIN + -I$(top_srcdir)/hugin -DDISABLE_HUGIN $(PTHREAD_CFLAGS) engine_CFLAGS = -DDISABLE_HUGIN engine_LDFLAGS = $(CPPUNIT_LIBS) $(DRUMGIZMO_LIBS) $(PTHREAD_LIBS) engine_SOURCES = \ diff --git a/test/audiocacheeventhandlertest.cc b/test/audiocacheeventhandlertest.cc new file mode 100644 index 0000000..5f30d2e --- /dev/null +++ b/test/audiocacheeventhandlertest.cc @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocacheeventhandlertest.cc + * + * Thu Jan 7 15:44:14 CET 2016 + * Copyright 2016 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include <cppunit/extensions/HelperMacros.h> + +#include <audiocacheeventhandler.h> + +class AudioCacheEventHandlerTest + : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AudioCacheEventHandlerTest); + CPPUNIT_TEST(threadedTest); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + void threadedTest() + { + AudioCacheIDManager id_manager; + id_manager.init(10); + + AudioCacheEventHandler event_handler(id_manager); + } +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheEventHandlerTest); diff --git a/test/audiocachefiletest.cc b/test/audiocachefiletest.cc new file mode 100644 index 0000000..98b7ab5 --- /dev/null +++ b/test/audiocachefiletest.cc @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocachefiletest.cc + * + * Thu Jan 7 15:43:12 CET 2016 + * Copyright 2016 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include <cppunit/extensions/HelperMacros.h> + +#include <cstring> + +#include <audiocachefile.h> +#include <audiofile.h> + +class TestableAudioCacheFiles + : public AudioCacheFiles +{ +public: + //CacheAudioFile& getAudioFile(const std::string& filename); + //void release(const std::string& filename); + int getRef(const std::string& filename) + { + if(audiofiles.find(filename) == audiofiles.end()) + { + return -1; + } + + return audiofiles[filename]->ref; + } +}; + +class AudioCacheFileTest + : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AudioCacheFileTest); + CPPUNIT_TEST(refTest); + CPPUNIT_TEST(readTest); + CPPUNIT_TEST(noFileTest); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + void refTest() + { + TestableAudioCacheFiles audiofiles; + std::string filename = "kit/ride-single-channel.wav"; + CPPUNIT_ASSERT_EQUAL(-1, audiofiles.getRef(filename)); + + audiofiles.getFile(filename); + CPPUNIT_ASSERT_EQUAL(1, audiofiles.getRef(filename)); + + audiofiles.getFile(filename); + CPPUNIT_ASSERT_EQUAL(2, audiofiles.getRef(filename)); + + audiofiles.releaseFile(filename); + CPPUNIT_ASSERT_EQUAL(1, audiofiles.getRef(filename)); + + audiofiles.releaseFile(filename); + CPPUNIT_ASSERT_EQUAL(-1, audiofiles.getRef(filename)); + } + + void readTestHelper(size_t buffer_size) + { + printf("Test buffer size: %d samples\n", (int)buffer_size); + + std::string filename = "kit/ride-multi-channel.wav"; + AudioFile* ref_file[13]; + for(size_t c = 0; c < 13; ++c) + { + ref_file[c] = new AudioFile(filename, c); + ref_file[c]->load(); + } + + AudioCacheFile file(filename); + CPPUNIT_ASSERT_EQUAL(filename, file.getFilename()); + CPPUNIT_ASSERT_EQUAL(13, (int)file.getChannelCount()); // Sanity check + + CacheChannels channels; + + sample_t samples[13][buffer_size]; + volatile bool ready[13]; + for(size_t c = 0; c < 13; ++c) + { + for(size_t i = 0; i < buffer_size; ++i) + { + samples[c][i] = 42; + } + + channels.push_back( + { + c, // channel + samples[c], // samples + buffer_size, // max_num_samples + &ready[c] // ready + } + ); + } + + for(size_t offset = 0; offset < file.getSize(); offset += buffer_size) + { + for(size_t c = 0; c < 13; ++c) + { + ready[c] = false; + } + + size_t read_size = file.getSize() - offset; + if(read_size > buffer_size) + { + read_size = buffer_size; + } + else + { + printf("Last read: %d samples\n", (int)read_size); + } + + file.readChunk(channels, offset, read_size); + + for(size_t c = 0; c < 13; ++c) + { + CPPUNIT_ASSERT_EQUAL(true, ready[c]?true:false); + } + + sample_t diff[13] = {0.0}; + for(size_t c = 0; c < 13; ++c) + { + for(size_t i = 0; i < read_size; ++i) + { + diff[c] += abs(ref_file[c]->data[i + offset] - samples[c][i]); + } + } + + for(int c = 0; c < 13; ++c) + { + CPPUNIT_ASSERT_EQUAL((sample_t)0.0, diff[c]); + } + } + + for(size_t c = 0; c < 13; ++c) + { + delete ref_file[c]; + } + } + + void readTest() + { + // Exhaustive test for 1...64 + for(size_t buffer_size = 1; buffer_size < 64; ++buffer_size) + { + readTestHelper(buffer_size); + } + + // Binary test for 64 .. 4096 + for(size_t buffer_size = 64; buffer_size < 4096; buffer_size *= 2) + { + readTestHelper(buffer_size); + } + + // And some sporadic tests for some "wierd" sizes. + for(size_t buffer_size = 65; buffer_size < 4096; buffer_size *= 1.1) + { + readTestHelper(buffer_size); + } + } + + void noFileTest() + { + size_t buffer_size = 64; + std::string filename = "kits/no-such-file.wav"; + + AudioCacheFile file(filename); + CPPUNIT_ASSERT_EQUAL(filename, file.getFilename()); + CPPUNIT_ASSERT_EQUAL(0u, (unsigned int)file.getSize()); + CPPUNIT_ASSERT_EQUAL(0u, (unsigned int)file.getChannelCount()); + + CacheChannels channels; + + sample_t samples[13][buffer_size]; + volatile bool ready[13]; + for(size_t c = 0; c < 13; ++c) + { + for(size_t i = 0; i < buffer_size; ++i) + { + samples[c][i] = 42.0f; + } + + channels.push_back( + { + c, // channel + samples[c], // samples + buffer_size, // max_num_samples + &ready[c] // ready + } + ); + } + + for(size_t c = 0; c < 13; ++c) + { + ready[c] = false; + } + + file.readChunk(channels, 0, buffer_size); + + for(size_t c = 0; c < 13; ++c) + { + CPPUNIT_ASSERT_EQUAL(false, ready[c]?true:false); + } + + for(size_t c = 0; c < 13; ++c) + { + for(size_t i = 0; i < buffer_size; ++i) + { + CPPUNIT_ASSERT_EQUAL(42.0f, samples[c][i]); + } + } + } +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheFileTest); diff --git a/test/audiocacheidmanagertest.cc b/test/audiocacheidmanagertest.cc new file mode 100644 index 0000000..a9cc878 --- /dev/null +++ b/test/audiocacheidmanagertest.cc @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * audiocacheidmanagertest.cc + * + * Thu Jan 7 15:42:31 CET 2016 + * Copyright 2016 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include <cppunit/extensions/HelperMacros.h> + +#include <audiocacheidmanager.h> + +class TestableAudioCacheIDManager + : public AudioCacheIDManager +{ +public: + int getAvailableIDs() + { + return availableids.size(); + } +}; + +class AudioCacheIDManagerTest + : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AudioCacheIDManagerTest); + CPPUNIT_TEST(registerReleaseTest); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + void registerReleaseTest() + { + TestableAudioCacheIDManager manager; + manager.init(2); + + cache_t c1; c1.afile = (AudioCacheFile*)1; + auto id1 = manager.registerID(c1); + CPPUNIT_ASSERT(id1 != CACHE_DUMMYID); + CPPUNIT_ASSERT(id1 != CACHE_NOID); + CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIDs()); + + cache_t c2; c2.afile = (AudioCacheFile*)2; + auto id2 = manager.registerID(c2); + CPPUNIT_ASSERT(id2 != CACHE_DUMMYID); + CPPUNIT_ASSERT(id2 != CACHE_NOID); + CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIDs()); + + cache_t c3; c3.afile = (AudioCacheFile*)3; + auto id3 = manager.registerID(c3); + CPPUNIT_ASSERT(id3 == CACHE_DUMMYID); + CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIDs()); + + cache_t& tc1 = manager.getCache(id1); + CPPUNIT_ASSERT_EQUAL(c1.afile, tc1.afile); + + cache_t& tc2 = manager.getCache(id2); + CPPUNIT_ASSERT_EQUAL(c2.afile, tc2.afile); + + manager.releaseID(id1); + CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIDs()); + + cache_t c4; c4.afile = (AudioCacheFile*)4; + auto id4 = manager.registerID(c4); + CPPUNIT_ASSERT(id4 != CACHE_DUMMYID); + CPPUNIT_ASSERT(id4 != CACHE_NOID); + CPPUNIT_ASSERT_EQUAL(0, manager.getAvailableIDs()); + + cache_t& tc4 = manager.getCache(id4); + CPPUNIT_ASSERT_EQUAL(c4.afile, tc4.afile); + + manager.releaseID(id2); + CPPUNIT_ASSERT_EQUAL(1, manager.getAvailableIDs()); + + manager.releaseID(id4); + CPPUNIT_ASSERT_EQUAL(2, manager.getAvailableIDs()); + } +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheIDManagerTest); diff --git a/test/audiocachetest.cc b/test/audiocachetest.cc new file mode 100644 index 0000000..5db5940 --- /dev/null +++ b/test/audiocachetest.cc @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * cachemanagertest.cc + * + * Sun Apr 19 10:15:59 CEST 2015 + * Copyright 2015 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include <cppunit/extensions/HelperMacros.h> + +#include <audiofile.h> +#include <audiocache.h> +#include <unistd.h> + +#define FRAMESIZE 64 + +class AudioCacheTest + : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AudioCacheTest); + CPPUNIT_TEST(singleChannelNonThreaded); + CPPUNIT_TEST(singleChannelThreaded); + CPPUNIT_TEST(multiChannelNonThreaded); + CPPUNIT_TEST(multiChannelThreaded); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + void tearDown() {} + + //! Test runner. + //! \param filename The name of the file to read. + //! \param channel The channel number to do comparison on. + //! \param thread Control if this test is running in threaded mode or not. + //! \param framesize The initial framesize to use. + void testHelper(const char* filename, int channel, bool threaded, + int framesize) + { + // Reference file: + AudioFile audio_file_ref(filename, channel); + printf("audio_file_ref.load\n"); + audio_file_ref.load(ALL_SAMPLES); + + // Input file: + AudioFile audio_file(filename, channel); + printf("audio_file.load\n"); + audio_file.load(4096); + + AudioCache audio_cache; + printf("audio_cache.init\n"); + audio_cache.init(100); + audio_cache.setAsyncMode(threaded); + + // Set initial (upper limit) framesize + audio_cache.setFrameSize(framesize); + + cacheid_t id; + + for(size_t initial_samples_needed = 0; + initial_samples_needed < (size_t)(framesize + 1); + ++initial_samples_needed) + { + + printf("open: initial_samples_needed: %d\n", (int)initial_samples_needed); + sample_t *samples = + audio_cache.open(&audio_file, initial_samples_needed, channel, id); + size_t size = initial_samples_needed; + size_t offset = 0; + + // Test pre cache: + for(size_t i = 0; i < size; ++i) + { + CPPUNIT_ASSERT_EQUAL(audio_file_ref.data[offset], samples[i]); + ++offset; + } + + // Test the rest + while(offset < audio_file_ref.size) + { + if(threaded) + { + // Wait until we are finished reading + int timeout = 1000; + while(!audio_cache.isReady(id)) + { + usleep(1000); + if(--timeout == 0) + { + CPPUNIT_ASSERT(false); // timeout + } + } + } + + samples = audio_cache.next(id, size); + + CPPUNIT_ASSERT_EQUAL(0, (int)audio_cache.getNumberOfUnderruns()); + + for(size_t i = 0; (i < size) && (offset < audio_file_ref.size); ++i) + { + if(audio_file_ref.data[offset] != samples[i]) + { + printf("-----> offset: %d, size: %d, diff: %d," + " i: %d, size: %d, block-diff: %d\n", + (int)offset, (int)audio_file_ref.size, + (int)(audio_file_ref.size - offset), + (int)i, (int)size, (int)(size - i)); + } + CPPUNIT_ASSERT_EQUAL(audio_file_ref.data[offset], samples[i]); + ++offset; + } + } + + audio_cache.close(id); + } + + printf("done\n"); + } + + void singleChannelNonThreaded() + { + printf("\nsinglechannel_nonthreaded()\n"); + const char filename[] = "kit/ride-single-channel.wav"; + int channel = 0; + bool threaded = false; + testHelper(filename, channel, threaded, FRAMESIZE); + } + + void singleChannelThreaded() + { + printf("\nsinglechannel_threaded()\n"); + const char filename[] = "kit/ride-single-channel.wav"; + int channel = 0; + bool threaded = true; + testHelper(filename, channel, threaded, FRAMESIZE); + } + + void multiChannelNonThreaded() + { + printf("\nmultichannel_nonthreaded()\n"); + const char filename[] = "kit/ride-multi-channel.wav"; + int channel = 0; + bool threaded = false; + testHelper(filename, channel, threaded, FRAMESIZE); + ++channel; + testHelper(filename, channel, threaded, FRAMESIZE); + } + + void multiChannelThreaded() + { + printf("\nmultichannel_threaded()\n"); + const char filename[] = "kit/ride-multi-channel.wav"; + int channel = 0; + bool threaded = true; + testHelper(filename, channel, threaded, FRAMESIZE); + ++channel; + testHelper(filename, channel, threaded, FRAMESIZE); + } + +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(AudioCacheTest); diff --git a/test/engine.cc b/test/engine.cc index 69d2a37..4c49a6c 100644 --- a/test/engine.cc +++ b/test/engine.cc @@ -43,6 +43,7 @@ public: AudioOutputEngine *oe = NULL; AudioInputEngine *ie = NULL; DrumGizmo dg(oe, ie); + dg.setFrameSize(100); // Switch kits emmidiately with giving the loader time to work: for(int i = 0; i < 100; i++) { diff --git a/test/kit/ride-multi-channel.wav b/test/kit/ride-multi-channel.wav Binary files differnew file mode 100644 index 0000000..3dec8a9 --- /dev/null +++ b/test/kit/ride-multi-channel.wav diff --git a/test/kit/ride-single-channel.wav b/test/kit/ride-single-channel.wav Binary files differnew file mode 100644 index 0000000..1760697 --- /dev/null +++ b/test/kit/ride-single-channel.wav diff --git a/test/lv2.cc b/test/lv2.cc index 32d896c..0ecf178 100644 --- a/test/lv2.cc +++ b/test/lv2.cc @@ -35,6 +35,12 @@ #define DG_URI "http://drumgizmo.org/lv2" +enum class Ports { + FreeWheel = 0, + MidiPort, + AudioPortOffset, +}; + /** * Tests that should be performed: * ------------------------------- @@ -86,7 +92,7 @@ public: res = h.verify(); CPPUNIT_ASSERT_EQUAL(0, res); - res = h.createInstance(); + res = h.createInstance(44100); CPPUNIT_ASSERT_EQUAL(0, res); const char config_fmt[] = @@ -127,7 +133,7 @@ public: // run for 1 samples to trigger kit loading res = h.run(1); CPPUNIT_ASSERT_EQUAL(0, res); - sleep(1); // wait for kit to get loaded (async), + usleep(1000); // wait for kit to get loaded (async), res = h.run(100); CPPUNIT_ASSERT_EQUAL(0, res); @@ -151,7 +157,7 @@ public: res = h.verify(); CPPUNIT_ASSERT_EQUAL(0, res); - res = h.createInstance(); + res = h.createInstance(44100); CPPUNIT_ASSERT_EQUAL(0, res); const char config_fmt[] = @@ -191,15 +197,19 @@ public: // Port buffers: char sequence_buffer[4096]; + bool freeWheel = false; + + // Free wheel port + res = h.connectPort((int)Ports::FreeWheel, (void*)&freeWheel); LV2TestHost::Sequence seq(sequence_buffer, sizeof(sequence_buffer)); - res = h.connectPort(0, seq.data()); + res = h.connectPort((int)Ports::MidiPort, seq.data()); CPPUNIT_ASSERT_EQUAL(0, res); // run for 1 samples to trigger kit loading res = h.run(1); CPPUNIT_ASSERT_EQUAL(0, res); - sleep(1); // wait for kit to get loaded (async), + usleep(1000); // wait for kit to get loaded (async), seq.addMidiNote(5, 1, 127); res = h.run(100); @@ -224,7 +234,7 @@ public: res = h.verify(); CPPUNIT_ASSERT_EQUAL(0, res); - res = h.createInstance(); + res = h.createInstance(44100); CPPUNIT_ASSERT_EQUAL(0, res); const char config_fmt[] = @@ -265,14 +275,22 @@ public: // Port buffers: char sequence_buffer[4096]; float pcm_buffer[16][10]; + bool freeWheel = true; + + // Free wheel port + res = h.connectPort((int)Ports::FreeWheel, (void*)&freeWheel); LV2TestHost::Sequence seq(sequence_buffer, sizeof(sequence_buffer)); - res = h.connectPort(0, seq.data()); + res = h.connectPort((int)Ports::MidiPort, seq.data()); CPPUNIT_ASSERT_EQUAL(0, res); - for(int i = 1; i <= 16; i++) { - memset(pcm_buffer, 1, sizeof(pcm_buffer)); - res += h.connectPort(i, pcm_buffer[i-1]); + for(int i = 0; i < 16; ++i) + { + for(int j = 0; j < 10; ++j) + { + pcm_buffer[i][j] = 0.42; + } + res += h.connectPort((int)Ports::AudioPortOffset + i, pcm_buffer[i]); } CPPUNIT_ASSERT_EQUAL(0, res); @@ -282,19 +300,19 @@ public: sleep(1); // wait for kit to get loaded (async), seq.addMidiNote(5, 1, 127); - for(int i = 0; i < 10; i++) { + for(int i = 0; i < 10; i++) + { res = h.run(10); + usleep(1000); CPPUNIT_ASSERT_EQUAL(0, res); - /* - printf("Iteration:\n"); - for(int k = 0; k < 4; k++) { - printf("#%d ", k); - for(int j = 0; j < 10; j++) printf("[%f]", pcm_buffer[k][j]); - printf("\n"); - } - printf("\n"); - */ + //printf("Iteration:\n"); + //for(int k = 0; k < 16; k++) { + // printf("#%d ", k); + // for(int j = 0; j < 10; j++) printf("[%f]", pcm_buffer[k][j]); + // printf("\n"); + //} + //printf("\n"); seq.clear(); } @@ -302,6 +320,7 @@ public: seq.addMidiNote(5, 1, 127); res = h.run(10); + usleep(1000); CPPUNIT_ASSERT_EQUAL(0, res); /* @@ -321,9 +340,11 @@ public: comp_val.u = 1040744448; // floating point value 0.133301.... - for(int k = 0; k < 4; k++) { - for(int j = 0; j < 10; j++) { - CPPUNIT_ASSERT_EQUAL(((j==0)?comp_val.f:0), pcm_buffer[k][j]); + for(int k = 0; k < 4; k++) + { + for(int j = 0; j < 10; j++) + { + CPPUNIT_ASSERT_EQUAL(((j==5)?comp_val.f:0), pcm_buffer[k][j]); } } seq.clear(); diff --git a/test/lv2_test_host.cc b/test/lv2_test_host.cc index 375ae40..9b4fc7a 100644 --- a/test/lv2_test_host.cc +++ b/test/lv2_test_host.cc @@ -42,6 +42,7 @@ #include <openssl/err.h> #include <openssl/evp.h> #include <string> + class Base64 { public: Base64() @@ -64,36 +65,44 @@ public: std::string write(const char *in, size_t size) { std::string out; - + BIO_write((BIO*)bio, in, size); size_t osize = BIO_ctrl_pending((BIO*)mbio); char *outbuf = (char*)malloc(osize); int len = BIO_read((BIO*)mbio, outbuf, osize); - if(len < 1) return ""; + if(len < 1) + { + return ""; + } + out.append(outbuf, len); free(outbuf); - + return out; } - + std::string flush() { std::string out; - + (void)BIO_flush((BIO*)bio); size_t size = BIO_ctrl_pending((BIO*)mbio); char *outbuf = (char*)malloc(size); int len = BIO_read((BIO*)mbio, outbuf, size); - if(len < 1) return ""; + if(len < 1) + { + return ""; + } + out.append(outbuf, len); free(outbuf); - + return out; } @@ -107,13 +116,15 @@ private: // TODO: Use map<int, std::string> instead -static char** uris = NULL; +static char** uris = nullptr; static size_t n_uris = 0; static LV2_URID map_uri(LV2_URID_Map_Handle handle, const char* uri) { - for(size_t i = 0; i < n_uris; ++i) { - if(!strcmp(uris[i], uri)) { + for(size_t i = 0; i < n_uris; ++i) + { + if(!strcmp(uris[i], uri)) + { return i + 1; } } @@ -126,22 +137,23 @@ static LV2_URID map_uri(LV2_URID_Map_Handle handle, const char* uri) static const char* unmap_uri(LV2_URID_Map_Handle handle, LV2_URID urid) { - if(urid > 0 && urid <= n_uris) { + if((urid > 0) && (urid <= n_uris)) + { return uris[urid - 1]; } - return NULL; + return nullptr; } -LV2_URID_Map map = { NULL, map_uri }; +LV2_URID_Map map = { nullptr, map_uri }; LV2_Feature map_feature = { LV2_URID_MAP_URI, &map }; -LV2_URID_Unmap unmap = { NULL, unmap_uri }; +LV2_URID_Unmap unmap = { nullptr, unmap_uri }; LV2_Feature unmap_feature = { LV2_URID_UNMAP_URI, &unmap }; -const LV2_Feature* features[] = { &map_feature, &unmap_feature, NULL }; +const LV2_Feature* features[] = { &map_feature, &unmap_feature, nullptr }; LV2TestHost::Sequence::Sequence(void *buffer, size_t buffer_size) { - this->buffer = buffer; - this->buffer_size = buffer_size; + this->buffer = buffer; + this->buffer_size = buffer_size; seq = (LV2_Atom_Sequence *)buffer; @@ -152,15 +164,14 @@ LV2TestHost::Sequence::Sequence(void *buffer, size_t buffer_size) } // Keep this to support atom extension from lv2 < 1.10 -static inline void -_lv2_atom_sequence_clear(LV2_Atom_Sequence* seq) +static inline void _lv2_atom_sequence_clear(LV2_Atom_Sequence* seq) { - seq->atom.size = sizeof(LV2_Atom_Sequence_Body); + seq->atom.size = sizeof(LV2_Atom_Sequence_Body); } void LV2TestHost::Sequence::clear() { - _lv2_atom_sequence_clear(seq); + _lv2_atom_sequence_clear(seq); } // Keep this to support atom extension from lv2 < 1.10 @@ -169,18 +180,19 @@ _lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq, uint32_t capacity, const LV2_Atom_Event* event) { - const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size; - - if (capacity - seq->atom.size < total_size) { - return NULL; - } - - LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size); - memcpy(e, event, total_size); - - seq->atom.size += lv2_atom_pad_size(total_size); - - return e; + const uint32_t total_size = (uint32_t)sizeof(*event) + event->body.size; + + if(capacity - seq->atom.size < total_size) + { + return nullptr; + } + + LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size); + memcpy(e, event, total_size); + + seq->atom.size += lv2_atom_pad_size(total_size); + + return e; } void LV2TestHost::Sequence::addMidiNote(uint64_t pos, @@ -197,66 +209,88 @@ void LV2TestHost::Sequence::addMidiNote(uint64_t pos, ev.event.time.frames = pos;// sample position ev.event.body.type = map.map(map.handle, LV2_MIDI__MidiEvent); ev.event.body.size = sizeof(ev.msg); - + ev.msg[0] = note_on; ev.msg[1] = key; ev.msg[2] = velocity; LV2_Atom_Event *e = _lv2_atom_sequence_append_event(seq, this->buffer_size, &ev.event); - (void)e; + (void)e; } void *LV2TestHost::Sequence::data() { - return buffer; + return buffer; } LV2TestHost::LV2TestHost(const char *lv2_path) { - if(lv2_path) { - setenv("LV2_PATH", lv2_path, 1); - } + if(lv2_path) + { + setenv("LV2_PATH", lv2_path, 1); + } world = lilv_world_new(); - if(world == NULL) return; + if(world == nullptr) + { + return; + } lilv_world_load_all(world); } LV2TestHost::~LV2TestHost() { - if(world) lilv_world_free(world); + if(world) + { + lilv_world_free(world); + } } int LV2TestHost::open(const char *plugin_uri) { - if(world == NULL) return 1; + if(world == nullptr) + { + return 1; + } plugins = lilv_world_get_all_plugins(world); - if(plugins == NULL) return 2; + if(plugins == nullptr) + { + return 2; + } uri = lilv_new_uri(world, plugin_uri); - if(uri == NULL) return 3; + if(uri == nullptr) + { + return 3; + } plugin = lilv_plugins_get_by_uri(plugins, uri); - if(plugin == NULL) return 4; - + if(plugin == nullptr) + { + return 4; + } - return 0; + return 0; } int LV2TestHost::verify() { bool verify = lilv_plugin_verify(plugin); - if(!verify) return 1; - return 0; + if(!verify) + { + return 1; + } + + return 0; } int LV2TestHost::close() { - // plugin is a const pointer; nothing to close here. - return 0; + // plugin is a const pointer; nothing to close here. + return 0; } /* // Get metadata @@ -330,29 +364,37 @@ int LV2TestHost::getPorts() } } */ -int LV2TestHost::createInstance() +int LV2TestHost::createInstance(size_t samplerate) { - instance = lilv_plugin_instantiate(plugin, 48000, features); - if(instance == NULL) return 1; - return 0; + instance = lilv_plugin_instantiate(plugin, samplerate, features); + if(instance == nullptr) + { + return 1; + } + + return 0; } int LV2TestHost::destroyInstance() { - if(instance) lilv_instance_free(instance); - return 0; + if(instance) + { + lilv_instance_free(instance); + } + + return 0; } int LV2TestHost::activate() { lilv_instance_activate(instance); - return 0; + return 0; } int LV2TestHost::deactivate() { lilv_instance_deactivate(instance); - return 0; + return 0; } int LV2TestHost::loadConfig(const char *config, size_t size) @@ -379,26 +421,26 @@ int LV2TestHost::loadConfig(const char *config, size_t size) { LilvState* restore_state = lilv_state_new_from_string(world, &map, ttl_config); - - lilv_state_restore(restore_state, instance, NULL, NULL, - LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, - features); + + lilv_state_restore(restore_state, instance, nullptr, nullptr, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, + features); } - return 0; + return 0; } int LV2TestHost::connectPort(int port, void *portdata) { - // if(lilv_port_is_a(p, port, lv2_ControlPort)) ... + // if(lilv_port_is_a(p, port, lv2_ControlPort)) ... lilv_instance_connect_port(instance, port, portdata); - return 0; + return 0; } int LV2TestHost::run(int num_samples) { - lilv_instance_run(instance, num_samples); - return 0; + lilv_instance_run(instance, num_samples); + return 0; } diff --git a/test/lv2_test_host.h b/test/lv2_test_host.h index f0677c7..81aa413 100644 --- a/test/lv2_test_host.h +++ b/test/lv2_test_host.h @@ -24,56 +24,53 @@ * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ -#ifndef __DRUMGIZMO_LV2_TEST_HOST_H__ -#define __DRUMGIZMO_LV2_TEST_HOST_H__ +#pragma once #include <lilv/lilv.h> #include <lv2/lv2plug.in/ns/ext/atom/atom.h> class LV2TestHost { public: - class Sequence { - public: - Sequence(void *buffer, size_t buffer_size); - void clear(); - void addMidiNote(uint64_t pos, uint8_t key, int8_t velocity); - void *data(); + class Sequence { + public: + Sequence(void *buffer, size_t buffer_size); + void clear(); + void addMidiNote(uint64_t pos, uint8_t key, int8_t velocity); + void *data(); - private: - void *buffer; - size_t buffer_size; - LV2_Atom_Sequence *seq; - }; + private: + void *buffer; + size_t buffer_size; + LV2_Atom_Sequence *seq; + }; - LV2TestHost(const char *lv2_path); - ~LV2TestHost(); + LV2TestHost(const char *lv2_path); + ~LV2TestHost(); - int open(const char *plugin_uri); - int close(); + int open(const char *plugin_uri); + int close(); - int verify(); + int verify(); - //void getMetadata(); - //int getPorts(); + //void getMetadata(); + //int getPorts(); - int createInstance(); - int destroyInstance(); + int createInstance(size_t samplerate); + int destroyInstance(); - int connectPort(int port, void *portdata); + int connectPort(int port, void *portdata); - int activate(); - int deactivate(); + int activate(); + int deactivate(); - int loadConfig(const char *config, size_t size); - int run(int num_samples); + int loadConfig(const char *config, size_t size); + int run(int num_samples); private: - LilvWorld* world; - const LilvPlugins* plugins; + LilvWorld* world; + const LilvPlugins* plugins; LilvNode* uri; const LilvPlugin* plugin; - LilvInstance* instance; + LilvInstance* instance; }; - -#endif/*__DRUMGIZMO_LV2_TEST_HOST_H__*/ diff --git a/vst/Makefile.mingw32.in b/vst/Makefile.mingw32.in index 089e869..1caf926 100644 --- a/vst/Makefile.mingw32.in +++ b/vst/Makefile.mingw32.in @@ -7,6 +7,10 @@ VST_SRC = \ VST_CFLAGS=-I$(VST_BASE) DG_SRC = \ + @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 \ @@ -35,7 +39,8 @@ DG_SRC = \ @top_srcdir@/src/thread.cc \ @top_srcdir@/src/velocity.cc \ @top_srcdir@/src/versionstr.cc -DG_CFLAGS = -I.. -I../include -I../src -DSSE -msse -msse2 -DDISABLE_HUGIN +DG_CFLAGS = -I.. -I../include -I../src -DSSE -msse -msse2 +# -DDISABLE_HUGIN GUI_SRC = \ @top_srcdir@/plugingui/dgwindow.cc \ @@ -79,7 +84,8 @@ DBG_SRC = \ @top_srcdir@/hugin/hugin.c \ @top_srcdir@/hugin/hugin_syslog.c -DBG_CFLAGS=-I../hugin -DWITH_HUG_SYSLOG -DWITH_HUG_MUTEX -DDISABLE_HUGIN +DBG_CFLAGS=-I../hugin -DWITH_HUG_SYSLOG -DWITH_HUG_MUTEX +# -DDISABLE_HUGIN # # http://old.nabble.com/using-VC%2B%2B-.lib-with-mingw-td23151303.html @@ -137,7 +143,7 @@ SRC = \ all: gcc $(DBG_CFLAGS) @top_srcdir@/hugin/hugin.c -c gcc $(DBG_CFLAGS) @top_srcdir@/hugin/hugin_syslog.c -c - g++ -std=c++11 -static -static-libgcc -O2 -g -Wall $(DBG_CFLAGS) $(DG_CFLAGS) $(DG_LIBS) $(VST_CFLAGS) hugin.o hugin_syslog.o $(DG_SRC) $(VST_SRC) ${SRC} ${GUI_SRC} ${GUI_CFLAGS} $(GUI_LIBS) $(EXPAT_CFLAGS) $(SRC_CFLAGS) $(ZITA_CXXFLAGS) $(EXPAT_LIBS) $(SNDFILE_CFLAGS) $(SNDFILE_LIBS) $(SRC_LIBS) $(ZITA_LIBS) -shared -o drumgizmo_vst.dll -Wl,--out-implib,libdrumgizmo_vst.a + g++ $(CXXFLAGS) -std=c++11 -static -static-libgcc -O2 -g -Wall $(DBG_CFLAGS) $(DG_CFLAGS) $(DG_LIBS) $(VST_CFLAGS) hugin.o hugin_syslog.o $(DG_SRC) $(VST_SRC) ${SRC} ${GUI_SRC} ${GUI_CFLAGS} $(GUI_LIBS) $(EXPAT_CFLAGS) $(SRC_CFLAGS) $(ZITA_CXXFLAGS) $(EXPAT_LIBS) $(SNDFILE_CFLAGS) $(SNDFILE_LIBS) $(SRC_LIBS) $(ZITA_LIBS) -shared -o drumgizmo_vst.dll -Wl,--out-implib,libdrumgizmo_vst.a clean: del -f drumgizmo_vst.dll libdrumgizmo_vst.a diff --git a/vst/drumgizmo_vst.cc b/vst/drumgizmo_vst.cc index d9cb975..c974a2a 100644 --- a/vst/drumgizmo_vst.cc +++ b/vst/drumgizmo_vst.cc @@ -32,266 +32,341 @@ #include <drumgizmo.h> #include <hugin.hpp> +#include <stdlib.h> +#include <string> #define NUM_PROGRAMS 0 #define NUM_PARAMS 0 -DGEditor::DGEditor(AudioEffect* effect) +DGEditor::DGEditor(AudioEffect* effect) { - DEBUG(dgeditor, "Create DGEditor\n"); - dgeff = (DrumGizmoVst*)effect; - plugingui = NULL; - drumgizmo = dgeff->drumgizmo; + DEBUG(dgeditor, "%s\n", __PRETTY_FUNCTION__); + dgeff = (DrumGizmoVst*)effect; + plugingui = nullptr; + drumgizmo = dgeff->drumgizmo; } bool DGEditor::open(void* ptr) { - DEBUG(dgeditor, "open GUI (new PluginGUI)\n"); - if(plugingui) delete plugingui; + DEBUG(dgeditor, "%s\n", __PRETTY_FUNCTION__); + if(plugingui) + { + delete plugingui; + } - plugingui = new GUI::PluginGUI(); - // plugingui->setChangeMidimapCallback(midimapHandler, dgeff); - - // plugingui->show(); - return true; + plugingui = new GUI::PluginGUI(); + plugingui->show(); + return true; } void DGEditor::close() { - DEBUG(dgeditor, "close GUI (delete PluginGUI)\n"); - // plugingui->hide(); - if(plugingui) delete plugingui; - plugingui = NULL; + DEBUG(dgeditor, "%s\n", __PRETTY_FUNCTION__); + + if(plugingui) + { + delete plugingui; + } + + plugingui = nullptr; } bool DGEditor::isOpen() { - DEBUG(vst, "isOpen\n"); - return plugingui != NULL; + DEBUG(dgeditor, "%s\n", __PRETTY_FUNCTION__); + return plugingui != nullptr; } void DGEditor::idle() { - DEBUG(vst, "idle\n"); - // if(plugingui) plugingui->processEvents(); + DEBUG(dgeditor, "%s\n", __PRETTY_FUNCTION__); + // if(plugingui) plugingui->processEvents(); } AudioEffect* createEffectInstance(audioMasterCallback audioMaster) { - DEBUG(vst, "createEffectInstance\n"); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); return new DrumGizmoVst(audioMaster); } DrumGizmoVst::DrumGizmoVst(audioMasterCallback audioMaster) - : AudioEffectX(audioMaster, NUM_PROGRAMS, NUM_PARAMS) + : AudioEffectX(audioMaster, NUM_PROGRAMS, NUM_PARAMS) { - hug_status_t status = hug_init(HUG_FLAG_OUTPUT_TO_SYSLOG | HUG_FLAG_USE_MUTEX, - HUG_OPTION_SYSLOG_HOST, "192.168.0.10", - HUG_OPTION_SYSLOG_PORT, 514, - HUG_OPTION_END); + hug_status_t status = HUG_STATUS_OK; + + int hugin_flags = HUG_FLAG_USE_MUTEX; + + const char* syslog_host_env = getenv("DG_SYSLOG_HOST"); + + if(syslog_host_env) + { + std::string syslog_host = syslog_host_env; + int syslog_port = 514; + const char* syslog_port_env = getenv("DG_SYSLOG_PORT"); + if(syslog_port_env) + { + syslog_port = atoi(syslog_port_env); + } + + status = hug_init(hugin_flags | HUG_FLAG_OUTPUT_TO_SYSLOG, + HUG_OPTION_SYSLOG_HOST, syslog_host.c_str(), + HUG_OPTION_SYSLOG_PORT, syslog_port, + HUG_OPTION_END); + } + else + { + status = hug_init(hugin_flags); + } + + if(status != HUG_STATUS_OK) + { + printf("Error: %d\n", status); + } - if(status != HUG_STATUS_OK) { - printf("Error: %d\n", status); - } + INFO(vst, "We are up and running"); - INFO(example, "We are up and running"); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); - DEBUG(vst, "DrumGizmoVst()\n"); + pos = 0; + buffer = nullptr; + buffer_size = 0; - pos = 0; - buffer = NULL; - buffer_size = 0; + output = nullptr; + input = nullptr; + drumgizmo = nullptr; - output = NULL; - input = NULL; - drumgizmo = NULL; - - output = new OutputVST(); - input = new InputVST(); - drumgizmo = new DrumGizmo(output, input); + output = new OutputVST(); + input = new InputVST(); + drumgizmo = new DrumGizmo(output, input); // initialize programs - //programs = new DrumGizmoVstProgram[kNumPrograms]; - //for(VstInt32 i = 0; i < 16; i++) channelPrograms[i] = i; + // programs = new DrumGizmoVstProgram[kNumPrograms]; + // for(VstInt32 i = 0; i < 16; i++) channelPrograms[i] = i; + + // if(programs) setProgram(0); - //if(programs) setProgram(0); - - if(audioMaster) { + if(audioMaster) + { setNumInputs(0); // no audio inputs setNumOutputs(NUM_OUTPUTS); canProcessReplacing(); isSynth(); - union { - char cid[4]; - unsigned int iid; - } id; + union + { + char cid[4]; + unsigned int iid; + } id; - memcpy(id.cid, "DGV5", 4); // Four bytes typecasted into an unsigned integer + memcpy(id.cid, "DGV5", 4); // Four bytes typecasted into an unsigned integer setUniqueID(id.iid); - // setUniqueID((unsigned int)time(NULL)); - + // setUniqueID((unsigned int)time(nullptr)); } initProcess(); suspend(); - editor = new DGEditor(this); - setEditor(editor); + editor = new DGEditor(this); + setEditor(editor); - programsAreChunks(true); + programsAreChunks(true); - // getChunk - // file:///home/deva/docs/c/drumgizmo/vst/vstsdk2.4/doc/html/class_audio_effect.html#42883c327783d7d31ed513b10c9204fc + // getChunk + // file:///home/deva/docs/c/drumgizmo/vst/vstsdk2.4/doc/html/class_audio_effect.html#42883c327783d7d31ed513b10c9204fc - // setChunk - // file:///home/deva/docs/c/drumgizmo/vst/vstsdk2.4/doc/html/class_audio_effect.html#b6e4c31c1acf8d1fc4046521912787b1 + // setChunk + // file:///home/deva/docs/c/drumgizmo/vst/vstsdk2.4/doc/html/class_audio_effect.html#b6e4c31c1acf8d1fc4046521912787b1 } DrumGizmoVst::~DrumGizmoVst() { - DEBUG(vst, "~DrumGizmoVst(1)\n"); - if(drumgizmo) delete drumgizmo; - DEBUG(vst, "~DrumGizmoVst(2)\n"); - if(input) delete input; - DEBUG(vst, "~DrumGizmoVst(3)\n"); - if(output) delete output; - DEBUG(vst, "~DrumGizmoVst(4)\n"); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + + DEBUG(vst, "~DrumGizmoVst(1)\n"); + if(drumgizmo) + { + delete drumgizmo; + } + + DEBUG(vst, "~DrumGizmoVst(2)\n"); + if(input) + { + delete input; + } + + DEBUG(vst, "~DrumGizmoVst(3)\n"); + if(output) + { + delete output; + } - hug_close(); + DEBUG(vst, "~DrumGizmoVst(4)\n"); + + hug_close(); } -VstInt32 DrumGizmoVst::getChunk(void **data, bool isPreset) +VstInt32 DrumGizmoVst::getChunk(void** data, bool isPreset) { - DEBUG(vst, "getChunk(data: %p isPreset: %d)\n", *data, isPreset?1:0); - std::string cfg = drumgizmo->configString(); - DEBUG(vst, "drumgizmo->config := %s\n", cfg.c_str()); - char *config = strdup(cfg.c_str()); - *data = config; - return cfg.length(); + DEBUG(vst, "%s - data: %p isPreset: %d\n", + __PRETTY_FUNCTION__, *data, isPreset ? 1 : 0); + std::string cfg = drumgizmo->configString(); + DEBUG(vst, "drumgizmo->config := %s\n", cfg.c_str()); + char* config = strdup(cfg.c_str()); + *data = config; + return cfg.length(); } -VstInt32 DrumGizmoVst::setChunk(void *data, VstInt32 byteSize, bool isPreset) +VstInt32 DrumGizmoVst::setChunk(void* data, VstInt32 byteSize, bool isPreset) { - std::string config; - config.append((const char*)data, (size_t)byteSize); - DEBUG(vst, "setChunk(isPreset: %d): [%d] %s\n", - isPreset?1:0, byteSize, config.c_str()); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); - if(!drumgizmo->setConfigString(config)) { - ERR(vst, "setConfigString failed...\n"); - return 1; - } + std::string config; + config.append((const char*)data, (size_t)byteSize); + DEBUG(vst, "setChunk(isPreset: %d): [%d] %s\n", isPreset ? 1 : 0, byteSize, + config.c_str()); - return 0; + if(!drumgizmo->setConfigString(config)) + { + ERR(vst, "setConfigString failed...\n"); + return 1; + } + + return 0; } -void DrumGizmoVst::setProgram(VstInt32 program) {} -void DrumGizmoVst::setProgramName(char* name) {} -void DrumGizmoVst::getProgramName(char* name) { name[0] = '\0'; } +void DrumGizmoVst::setProgram(VstInt32 program) +{ + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); +} +void DrumGizmoVst::setProgramName(char* name) +{ + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); +} +void DrumGizmoVst::getProgramName(char* name) +{ + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + name[0] = '\0'; +} void DrumGizmoVst::getParameterLabel(VstInt32 index, char* label) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); label[0] = '\0'; /* switch(index) { - case kWaveform1: - case kWaveform2: - vst_strncpy(label, "Shape", kVstMaxParamStrLen); - break; - - case kFreq1: - case kFreq2: - vst_strncpy(label, "Hz", kVstMaxParamStrLen); - break; - - case kVolume1: - case kVolume2: - case kVolume: - vst_strncpy(label, "dB", kVstMaxParamStrLen); - break; + case kWaveform1: + case kWaveform2: + vst_strncpy(label, "Shape", kVstMaxParamStrLen); + break; + + case kFreq1: + case kFreq2: + vst_strncpy(label, "Hz", kVstMaxParamStrLen); + break; + + case kVolume1: + case kVolume2: + case kVolume: + vst_strncpy(label, "dB", kVstMaxParamStrLen); + break; } */ } void DrumGizmoVst::getParameterDisplay(VstInt32 index, char* text) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); text[0] = 0; /* switch(index) { - case kWaveform1: - if(fWaveform1 < .5) - vst_strncpy(text, "Sawtooth", kVstMaxParamStrLen); - else - vst_strncpy(text, "Pulse", kVstMaxParamStrLen); - break; - - case kFreq1: float2string(fFreq1, text, kVstMaxParamStrLen); break; - case kVolume1: dB2string(fVolume1, text, kVstMaxParamStrLen); break; - - case kWaveform2: - if(fWaveform2 < .5) - vst_strncpy(text, "Sawtooth", kVstMaxParamStrLen); - else - vst_strncpy(text, "Pulse", kVstMaxParamStrLen); - break; - - case kFreq2: float2string(fFreq2, text, kVstMaxParamStrLen); break; - case kVolume2: dB2string(fVolume2, text, kVstMaxParamStrLen); break; - case kVolume: dB2string(fVolume, text, kVstMaxParamStrLen); break; + case kWaveform1: + if(fWaveform1 < .5) + vst_strncpy(text, "Sawtooth", kVstMaxParamStrLen); + else + vst_strncpy(text, "Pulse", kVstMaxParamStrLen); + break; + + case kFreq1: float2string(fFreq1, text, kVstMaxParamStrLen); + break; + case kVolume1: dB2string(fVolume1, text, kVstMaxParamStrLen); + break; + + case kWaveform2: + if(fWaveform2 < .5) + vst_strncpy(text, "Sawtooth", kVstMaxParamStrLen); + else + vst_strncpy(text, "Pulse", kVstMaxParamStrLen); + break; + + case kFreq2: float2string(fFreq2, text, kVstMaxParamStrLen); + break; + case kVolume2: dB2string(fVolume2, text, kVstMaxParamStrLen); + break; + case kVolume: dB2string(fVolume, text, kVstMaxParamStrLen); + break; } */ } void DrumGizmoVst::getParameterName(VstInt32 index, char* label) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); /* switch(index) { - case kWaveform1: vst_strncpy(label, "Wave 1", kVstMaxParamStrLen); break; - case kFreq1: vst_strncpy(label, "Freq 1", kVstMaxParamStrLen); break; - case kVolume1: vst_strncpy(label, "Levl 1", kVstMaxParamStrLen); break; - case kWaveform2: vst_strncpy(label, "Wave 2", kVstMaxParamStrLen); break; - case kFreq2: vst_strncpy(label, "Freq 2", kVstMaxParamStrLen); break; - case kVolume2: vst_strncpy(label, "Levl 2", kVstMaxParamStrLen); break; - case kVolume: vst_strncpy(label, "Volume", kVstMaxParamStrLen); break; + case kWaveform1: vst_strncpy(label, "Wave 1", kVstMaxParamStrLen); + break; + case kFreq1: vst_strncpy(label, "Freq 1", kVstMaxParamStrLen); + break; + case kVolume1: vst_strncpy(label, "Levl 1", kVstMaxParamStrLen); + break; + case kWaveform2: vst_strncpy(label, "Wave 2", kVstMaxParamStrLen); + break; + case kFreq2: vst_strncpy(label, "Freq 2", kVstMaxParamStrLen); + break; + case kVolume2: vst_strncpy(label, "Levl 2", kVstMaxParamStrLen); + break; + case kVolume: vst_strncpy(label, "Volume", kVstMaxParamStrLen); + break; } */ } void DrumGizmoVst::setParameter(VstInt32 index, float value) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); /* DrumGizmoVstProgram *ap = &programs[curProgram]; switch(index) { - case kWaveform1: fWaveform1 = ap->fWaveform1 = value; break; - case kFreq1: fFreq1 = ap->fFreq1 = value; break; - case kVolume1: fVolume1 = ap->fVolume1 = value; break; - case kWaveform2: fWaveform2 = ap->fWaveform2 = value; break; - case kFreq2: fFreq2 = ap->fFreq2 = value; break; - case kVolume2: fVolume2 = ap->fVolume2 = value; break; - case kVolume: fVolume = ap->fVolume = value; break; + case kWaveform1: fWaveform1 = ap->fWaveform1 = value; break; + case kFreq1: fFreq1 = ap->fFreq1 = value; break; + case kVolume1: fVolume1 = ap->fVolume1 = value; break; + case kWaveform2: fWaveform2 = ap->fWaveform2 = value; break; + case kFreq2: fFreq2 = ap->fFreq2 = value; break; + case kVolume2: fVolume2 = ap->fVolume2 = value; break; + case kVolume: fVolume = ap->fVolume = value; break; } */ } float DrumGizmoVst::getParameter(VstInt32 index) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); float value = 0; /* switch(index) { - case kWaveform1: value = fWaveform1; break; - case kFreq1: value = fFreq1; break; - case kVolume1: value = fVolume1; break; - case kWaveform2: value = fWaveform2; break; - case kFreq2: value = fFreq2; break; - case kVolume2: value = fVolume2; break; - case kVolume: value = fVolume; break; + case kWaveform1: value = fWaveform1; break; + case kFreq1: value = fFreq1; break; + case kVolume1: value = fVolume1; break; + case kWaveform2: value = fWaveform2; break; + case kFreq2: value = fFreq2; break; + case kVolume2: value = fVolume2; break; + case kVolume: value = fVolume; break; } */ return value; @@ -300,6 +375,7 @@ float DrumGizmoVst::getParameter(VstInt32 index) bool DrumGizmoVst::getOutputProperties(VstInt32 index, VstPinProperties* properties) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); if(index < NUM_OUTPUTS) { vst_strncpy(properties->label, "Channel ", 63); @@ -317,75 +393,105 @@ bool DrumGizmoVst::getOutputProperties(VstInt32 index, bool DrumGizmoVst::getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); return false; } bool DrumGizmoVst::getEffectName(char* name) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); vst_strncpy(name, "DrumGizmo4", kVstMaxEffectNameLen); return true; } bool DrumGizmoVst::getVendorString(char* text) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); vst_strncpy(text, "Aasimon.org", kVstMaxVendorStrLen); return true; } bool DrumGizmoVst::getProductString(char* text) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); vst_strncpy(text, "Vst Synth", kVstMaxProductStrLen); return true; } VstInt32 DrumGizmoVst::getVendorVersion() -{ - return 1000; +{ + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + return 1000; } VstInt32 DrumGizmoVst::canDo(char* text) { - if(!strcmp(text, "receiveVstEvents")) return 1; - if(!strcmp(text, "receiveVstMidiEvent")) return 1; - //if(!strcmp(text, "midiProgramNames")) return 1; - return -1; // explicitly can't do; 0 => don't know + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + if(!strcmp(text, "receiveVstEvents")) + { + return 1; + } + + if(!strcmp(text, "receiveVstMidiEvent")) + { + return 1; + } + + // if(!strcmp(text, "midiProgramNames")) return 1; + return -1; // explicitly can't do; 0 => don't know } VstInt32 DrumGizmoVst::getNumMidiInputChannels() { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); return 1; // we are monophonic } VstInt32 DrumGizmoVst::getNumMidiOutputChannels() { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); return 0; // no MIDI output back to Host app } VstInt32 DrumGizmoVst::getMidiProgramName(VstInt32 channel, MidiProgramName* mpn) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); VstInt32 prg = mpn->thisProgramIndex; - if(prg < 0 || prg >= 128) + if((prg < 0) || (prg >= 128)) + { return 0; + } + fillProgram(channel, prg, mpn); if(channel == 9) + { return 1; + } + return 128L; } VstInt32 DrumGizmoVst::getCurrentMidiProgram(VstInt32 channel, MidiProgramName* mpn) { - if(channel < 0 || channel >= 16 || !mpn) return -1; + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + if((channel < 0) || (channel >= 16) || !mpn) + { + return -1; + } + VstInt32 prg = 0; mpn->thisProgramIndex = prg; fillProgram(channel, prg, mpn); + return prg; } void DrumGizmoVst::fillProgram(VstInt32 channel, VstInt32 prg, MidiProgramName* mpn) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); mpn->midiBankMsb = mpn->midiBankLsb = -1; mpn->reserved = 0; mpn->flags = 0; @@ -398,16 +504,17 @@ void DrumGizmoVst::fillProgram(VstInt32 channel, VstInt32 prg, VstInt32 DrumGizmoVst::getMidiProgramCategory(VstInt32 channel, MidiProgramCategory* cat) { - cat->parentCategoryIndex = -1; // -1:no parent category - cat->flags = 0; // reserved, none defined yet, zero. - // VstInt32 category = cat->thisCategoryIndex; + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + cat->parentCategoryIndex = -1; // -1:no parent category + cat->flags = 0; // reserved, none defined yet, zero. + // VstInt32 category = cat->thisCategoryIndex; vst_strncpy(cat->name, "Drums", 63); return 1; } bool DrumGizmoVst::hasMidiProgramsChanged(VstInt32 channel) { - return false; // updateDisplay() + return false; // updateDisplay() } bool DrumGizmoVst::getMidiKeyName(VstInt32 channel, MidiKeyName* key) @@ -416,49 +523,68 @@ bool DrumGizmoVst::getMidiKeyName(VstInt32 channel, MidiKeyName* key) // if keyName is "" the standard name of the key will be displayed. // if false is returned, no MidiKeyNames defined for 'thisProgramIndex'. { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); // key->thisProgramIndex; // >= 0. fill struct for this program index. // key->thisKeyNumber; // 0 - 127. fill struct for this key number. key->keyName[0] = 0; - key->reserved = 0; // zero - key->flags = 0; // reserved, none defined yet, zero. + key->reserved = 0; // zero + key->flags = 0; // reserved, none defined yet, zero. return false; } void DrumGizmoVst::setSampleRate(float sampleRate) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); AudioEffectX::setSampleRate(sampleRate); - drumgizmo->setSamplerate(sampleRate); + drumgizmo->setSamplerate(sampleRate); } void DrumGizmoVst::setBlockSize(VstInt32 blockSize) { + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); AudioEffectX::setBlockSize(blockSize); } void DrumGizmoVst::initProcess() { - // drumgizmo->loadkit(getenv("DRUMGIZMO_DRUMKIT")); - drumgizmo->init(); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + // drumgizmo->loadkit(getenv("DRUMGIZMO_DRUMKIT")); + drumgizmo->init(); } void DrumGizmoVst::processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames) { - output->setOutputs(outputs); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + long lvl = getCurrentProcessLevel(); + // 0 = realtime/normal + // 1 = non-realtime/rendering + // 2 = offline processing + drumgizmo->setFreeWheel(lvl != 0); - if(buffer_size != (size_t)sampleFrames) { - if(buffer) free(buffer); - buffer_size = sampleFrames; - buffer = (sample_t*)malloc(sizeof(sample_t) * buffer_size); - } + output->setOutputs(outputs); + + if(buffer_size != (size_t)sampleFrames) + { + if(buffer) + { + free(buffer); + } + + buffer_size = sampleFrames; + buffer = (sample_t*)malloc(sizeof(sample_t) * buffer_size); + + drumgizmo->setFrameSize(buffer_size); + } - drumgizmo->run(pos, buffer, buffer_size); + drumgizmo->run(pos, buffer, buffer_size); - pos += sampleFrames; + pos += sampleFrames; } VstInt32 DrumGizmoVst::processEvents(VstEvents* ev) { - input->processEvents(ev); + DEBUG(vst, "%s\n", __PRETTY_FUNCTION__); + input->processEvents(ev); return 1; } |