From a0e2b9398a06ca2ea164c2ffd6fd89f713b93598 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Fri, 3 Aug 2018 20:02:04 +0200 Subject: Add support for partial buffers in cache and rendering engine - fixes dropouts on framesize changes for example when looping. --- src/audiocache.cc | 27 ++---- src/drumgizmo.cc | 155 ++++++++++++++++------------------ src/drumgizmo.h | 1 + src/events.h | 12 +-- test/dgreftest/compareoutputengine.cc | 21 +++-- test/dgreftest/compareoutputengine.h | 2 + test/dgreftest/dgreftest.cc | 24 +++++- 7 files changed, 128 insertions(+), 114 deletions(-) diff --git a/src/audiocache.cc b/src/audiocache.cc index 2e9eaf8..a2d26d9 100644 --- a/src/audiocache.cc +++ b/src/audiocache.cc @@ -152,8 +152,6 @@ sample_t* AudioCache::open(const AudioFile& file, sample_t* AudioCache::next(cacheid_t id, std::size_t& size) { - size = framesize; - if(id == CACHE_DUMMYID) { settings.number_of_underruns.fetch_add(1); @@ -165,19 +163,14 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) if(c.preloaded_samples) { - // We are playing from memory: if(c.localpos < c.preloaded_samples_size) { sample_t* s = c.preloaded_samples + c.localpos; + // If only a partial frame is returned. Reflect this in the size + size = std::min(size, c.preloaded_samples_size - c.localpos); - if((c.localpos + framesize) > c.preloaded_samples_size) - { - // Only a partial frame is returned. Reflect this in the size - size = c.preloaded_samples_size - c.localpos; - } - - c.localpos += framesize; + c.localpos += size; return s; } @@ -186,7 +179,6 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) } else { - // We are playing from cache: if(c.localpos < chunk_size) { @@ -194,13 +186,15 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) { // Just return silence. settings.number_of_underruns.fetch_add(1); - c.localpos += framesize; // Skip these samples so we don't loose sync. + c.localpos += size; // Skip these samples so we don't loose sync. assert(nodata); return nodata; } sample_t* s = c.front + c.localpos; - c.localpos += framesize; + // If only a partial frame is returned. Reflect this in the size + size = std::min(size, chunk_size - c.localpos); + c.localpos += size; return s; } } @@ -210,7 +204,7 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) { // Just return silence. settings.number_of_underruns.fetch_add(1); - c.localpos += framesize; // Skip these samples so we don't loose sync. + c.localpos += size; // Skip these samples so we don't loose sync. assert(nodata); return nodata; } @@ -219,7 +213,7 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) std::swap(c.front, c.back); // Next time we go here we have already read the first frame. - c.localpos = framesize; + c.localpos = size; c.pos += chunk_size; @@ -239,7 +233,6 @@ sample_t* AudioCache::next(cacheid_t id, std::size_t& size) // We should always have a front buffer at this point. assert(c.front); - return c.front; } @@ -266,8 +259,6 @@ void AudioCache::close(cacheid_t id) void AudioCache::setFrameSize(std::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 event_handler_lock(event_handler); diff --git a/src/drumgizmo.cc b/src/drumgizmo.cc index 2d3e410..81db5c5 100644 --- a/src/drumgizmo.cc +++ b/src/drumgizmo.cc @@ -93,12 +93,6 @@ void DrumGizmo::setFrameSize(size_t framesize) this->framesize = framesize; - // Remove all active events as they are cached using the old framesize. - for(std::size_t ch = 0; ch < MAX_NUM_CHANNELS; ++ch) - { - activeevents[ch].clear(); - } - // Update framesize in drumkitloader and cachemanager: loader.setFrameSize(framesize); audio_cache.setFrameSize(framesize); @@ -240,11 +234,69 @@ bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples) return true; } -#undef SSE // SSE broken for now ... so disable it. -#ifdef SSE -#define N 8 -typedef float vNsf __attribute__ ((vector_size(sizeof(sample_t)*N))); -#endif/*SSE*/ +void DrumGizmo::renderSampleEvent(EventSample& evt, int pos, sample_t *s, std::size_t sz) +{ + 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) > evt.sample_size) + { + end = evt.sample_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 + +repeat: + float scale = 1.0f; + for(; (n < end) && (t < (evt.buffer_size - evt.buffer_ptr)); ++n) + { + assert(n >= 0); + assert(n < sz); + + assert(t >= 0); + assert(t < evt.buffer_size - evt.buffer_ptr); + + if(evt.rampdownInProgress() && evt.rampdown_count > 0) + { + scale = std::min((float)evt.rampdown_count/evt.ramp_length, 1.f); + evt.rampdown_count--; + } + + s[n] += evt.buffer[evt.buffer_ptr + t] * evt.scale * scale; + ++t; + } + + // Add internal buffer counter to "global" event counter. + evt.t += t;//evt.buffer_size; + evt.buffer_ptr += t; + + if(n != sz && evt.t < evt.sample_size) + { + evt.buffer_size = sz - n;// Hint new size + + // More samples needed for current buffer + evt.buffer = audio_cache.next(evt.cache_id, evt.buffer_size); + + evt.buffer_ptr = 0; + t = 0; + goto repeat; + } +} void DrumGizmo::getSamples(int ch, int pos, sample_t* s, size_t sz) { @@ -260,7 +312,8 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t* s, size_t sz) Event* event = *i; Event::type_t type = event->getType(); - switch(type) { + switch(type) + { case Event::sample: { EventSample& evt = *static_cast(event); @@ -272,9 +325,9 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t* s, size_t sz) break; } - // Don't handle event now is is scheduled for a future iteration? if(evt.offset > (pos + sz)) { + // Don't handle event now. It is scheduled for a future iteration. continue; } @@ -290,86 +343,24 @@ void DrumGizmo::getSamples(int ch, int pos, sample_t* s, size_t sz) } evt.buffer_size = initial_chunksize; + evt.sample_size = af.size; } { std::lock_guard guard(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; - } + renderSampleEvent(evt, pos, s, sz); - // This should not be necessary but make absolutely sure that we do - // not write over the end of the buffer. - if(end > sz) + if((evt.t >= evt.sample_size) || (evt.rampdown_count == 0)) { - end = sz; - } - - size_t t = 0; // Internal buffer counter - if(!evt.rampdownInProgress()) - { -#ifdef SSE - 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]) * evt.scale; - t += N; - } -#endif - for(; (n < end) && (t < evt.buffer_size); ++n) - { - assert(n >= 0); - assert(n < sz); - - assert(t >= 0); - assert(t < evt.buffer_size); - - s[n] += evt.buffer[t] * evt.scale; - ++t; - } - } - else - { // Ramp down in progress. - for(; (n < end) && (t < evt.buffer_size) && evt.rampdown_count; ++n) - { - float scale = std::min((float)evt.rampdown_count/evt.ramp_length, 1.f); - s[n] += evt.buffer[t] * evt.scale * scale; - ++t; - evt.rampdown_count--; - } + removeevent = true; } - // Add internal buffer counter to "global" event counter. - evt.t += evt.buffer_size; - - if((evt.t < af.size) && (evt.rampdown_count != 0)) + if(evt.buffer_ptr >= evt.buffer_size && removeevent == false) { + evt.buffer_size = sz; evt.buffer = audio_cache.next(evt.cache_id, evt.buffer_size); - } - else - { - removeevent = true; + evt.buffer_ptr = 0; } if(removeevent) diff --git a/src/drumgizmo.h b/src/drumgizmo.h index 562e2ba..0f2c20e 100644 --- a/src/drumgizmo.h +++ b/src/drumgizmo.h @@ -56,6 +56,7 @@ public: bool run(size_t pos, sample_t *samples, size_t nsamples); void stop(); + void renderSampleEvent(EventSample& evt, int pos, sample_t *s, std::size_t sz); void getSamples(int ch, int pos, sample_t *s, size_t sz); //! Get the current engine latency in samples. diff --git a/src/events.h b/src/events.h index b78dd79..c28283e 100644 --- a/src/events.h +++ b/src/events.h @@ -31,8 +31,6 @@ #include #include -#include - #include "audiofile.h" #include "audio.h" #include "audiocache.h" @@ -62,7 +60,8 @@ public: timepos_t offset; //< Global position (ie. not relative to buffer) }; -class EventSample : public Event +class EventSample + : public Event { public: EventSample(channel_t c, float g, AudioFile* af, @@ -91,10 +90,12 @@ public: cacheid_t cache_id; sample_t* buffer; - size_t buffer_size; + std::size_t buffer_size; + std::size_t buffer_ptr{0}; //< Internal pointer into the current buffer + std::size_t sample_size{0}; //< Total number of audio samples in this sample. float gain; - unsigned int t; + unsigned int t; //< Internal sample position. AudioFile* file; std::string group; void* instrument; @@ -118,4 +119,3 @@ private: std::multimap queue; std::mutex mutex; }; - diff --git a/test/dgreftest/compareoutputengine.cc b/test/dgreftest/compareoutputengine.cc index 33dfe2a..04145b0 100644 --- a/test/dgreftest/compareoutputengine.cc +++ b/test/dgreftest/compareoutputengine.cc @@ -33,6 +33,7 @@ CompareOutputEngine::CompareOutputEngine() , info{} , file{"output"} { + info = {}; info.samplerate = 44100; info.channels = 1; info.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; @@ -44,9 +45,9 @@ CompareOutputEngine::~CompareOutputEngine() sf_close(handle); } -bool CompareOutputEngine::init(const Channels& data) +bool CompareOutputEngine::init(const Channels& channels) { - info.channels = data.size(); + info.channels = channels.size(); handle = sf_open(file.c_str(), SFM_READ, &info); if(handle == nullptr) @@ -116,8 +117,11 @@ void CompareOutputEngine::run(int ch, sample_t* samples, size_t nsamples) void CompareOutputEngine::post(size_t nsamples) { - sample_t ref_buffer[sizeof(buffer) / sizeof(sample_t)]; - sf_readf_float(handle, ref_buffer, nsamples); + nsamples = sf_readf_float(handle, ref_buffer, nsamples); + if(nsamples == 0) + { + return; + } for(std::size_t i = 0; i < nsamples; ++i) { @@ -126,10 +130,17 @@ void CompareOutputEngine::post(size_t nsamples) if(buffer[i * info.channels + ch] != ref_buffer[i * info.channels + ch]) { ++diff_samples; + + // Use this to quit on first bad sample. + //std::cerr << "ch: " << ch << ", pos: " << pos + i << + // " expected: " << ref_buffer[i * info.channels + ch] << + // " got: " << buffer[i * info.channels + ch] << std::endl; + //exit(1); + } } } - + pos += nsamples; } size_t CompareOutputEngine::getSamplerate() const diff --git a/test/dgreftest/compareoutputengine.h b/test/dgreftest/compareoutputengine.h index a82116c..89a3a83 100644 --- a/test/dgreftest/compareoutputengine.h +++ b/test/dgreftest/compareoutputengine.h @@ -53,4 +53,6 @@ private: std::string file; sample_t buffer[4096 * 16]; std::size_t diff_samples{0}; + sample_t ref_buffer[sizeof(buffer) / sizeof(sample_t)]; + size_t pos{0}; }; diff --git a/test/dgreftest/dgreftest.cc b/test/dgreftest/dgreftest.cc index ea38091..a4eb897 100644 --- a/test/dgreftest/dgreftest.cc +++ b/test/dgreftest/dgreftest.cc @@ -135,14 +135,32 @@ int main(int argc, char* argv[]) size_t nsamples = oe->getBufferSize(); sample_t *samples = (sample_t *)malloc(nsamples * sizeof(sample_t)); - drumgizmo.setFrameSize(oe->getBufferSize()); + drumgizmo.setFrameSize(nsamples); ie.start(); oe->start(); - while(drumgizmo.run(pos, samples, nsamples) == true) + size_t framesize = nsamples; + int dir = -1; + while(drumgizmo.run(pos, samples, framesize) == true) { - pos += nsamples; + pos += framesize; + + framesize += dir; + + if(framesize < 1) + { + framesize = 1; + dir = 1; + } + + if(framesize >= nsamples) + { + framesize = nsamples; + dir = -1; + } + + drumgizmo.setFrameSize(framesize); } ie.stop(); -- cgit v1.2.3