Mercurial > hg > svapp
changeset 738:48001ed9143b audio-source-refactor
Introduce TimeStretchWrapper; some work towards making the AudioCallbackPlaySource not actually try to be an ApplicationPlaybackSource itself but only return one that is constructed from wrappers that it controls the lifespan of
author | Chris Cannam |
---|---|
date | Wed, 18 Mar 2020 12:51:41 +0000 |
parents | 497d80d3b9c4 |
children | ddfac001b543 |
files | audio/AudioCallbackPlaySource.cpp audio/AudioCallbackPlaySource.h audio/TimeStretchWrapper.cpp audio/TimeStretchWrapper.h files.pri framework/MainWindowBase.cpp framework/MainWindowBase.h |
diffstat | 7 files changed, 426 insertions(+), 272 deletions(-) [+] |
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp Wed Feb 05 12:33:24 2020 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Wed Mar 18 12:51:41 2020 +0000 @@ -16,6 +16,7 @@ #include "AudioCallbackPlaySource.h" #include "AudioGenerator.h" +#include "TimeStretchWrapper.h" #include "data/model/Model.h" #include "base/ViewManagerBase.h" @@ -32,9 +33,6 @@ #include "bqvec/VectorOps.h" -#include <rubberband/RubberBandStretcher.h> -using namespace RubberBand; - using breakfastquay::v_zero_channels; #include <iostream> @@ -78,15 +76,9 @@ m_auditioningPluginFailed(false), m_playStartFrame(0), m_playStartFramePassed(false), - m_timeStretcher(nullptr), - m_monoStretcher(nullptr), - m_stretchRatio(1.0), - m_stretchMono(false), - m_stretcherInputCount(0), - m_stretcherInputs(nullptr), - m_stretcherInputSizes(nullptr), m_fillThread(nullptr), - m_resamplerWrapper(nullptr) + m_resamplerWrapper(nullptr), + m_timeStretchWrapper(nullptr) { m_viewManager->setAudioPlaySource(this); @@ -135,15 +127,6 @@ delete m_audioGenerator; - for (int i = 0; i < m_stretcherInputCount; ++i) { - delete[] m_stretcherInputs[i]; - } - delete[] m_stretcherInputSizes; - delete[] m_stretcherInputs; - - delete m_timeStretcher; - delete m_monoStretcher; - m_bufferScavenger.scavenge(true); m_pluginScavenger.scavenge(true); #ifdef DEBUG_AUDIO_PLAY_SOURCE @@ -151,6 +134,32 @@ #endif } +breakfastquay::ApplicationPlaybackSource * +AudioCallbackPlaySource::getApplicationPlaybackSource() +{ + QMutexLocker locker(&m_mutex); + + if (m_timeStretchWrapper) { + return m_timeStretchWrapper; + } + + checkWrappers(); + return m_timeStretchWrapper; +} + +void +AudioCallbackPlaySource::checkWrappers() +{ + // to be called only with m_mutex held + + if (!m_resamplerWrapper) { + m_resamplerWrapper = new breakfastquay::ResamplerWrapper(this); + } + if (!m_timeStretchWrapper) { + m_timeStretchWrapper = new TimeStretchWrapper(m_resamplerWrapper); + } +} + void AudioCallbackPlaySource::addModel(ModelId modelId) { @@ -250,24 +259,14 @@ if (srChanged) { - SVCERR << "AudioCallbackPlaySource: Source rate changed" << endl; + checkWrappers(); - if (m_resamplerWrapper) { - SVCERR << "AudioCallbackPlaySource: Source sample rate changed to " - << m_sourceSampleRate << ", updating resampler wrapper" << endl; - m_resamplerWrapper->changeApplicationSampleRate - (int(round(m_sourceSampleRate))); - m_resamplerWrapper->reset(); - } - - delete m_timeStretcher; - delete m_monoStretcher; - m_timeStretcher = nullptr; - m_monoStretcher = nullptr; - - if (m_stretchRatio != 1.f) { - setTimeStretch(m_stretchRatio); - } + SVCERR << "AudioCallbackPlaySource: Source sample rate changed to " + << m_sourceSampleRate << ", updating resampler wrapper" + << endl; + m_resamplerWrapper->changeApplicationSampleRate + (int(round(m_sourceSampleRate))); + m_resamplerWrapper->reset(); } rebuildRangeLists(); @@ -483,11 +482,8 @@ m_mutex.lock(); - if (m_timeStretcher) { - m_timeStretcher->reset(); - } - if (m_monoStretcher) { - m_monoStretcher->reset(); + if (m_timeStretchWrapper) { + m_timeStretchWrapper->reset(); } m_readBufferFill = m_writeBufferFill = startFrame; @@ -605,20 +601,13 @@ emit audioOverloadPluginDisabled(); return; } - - if (m_timeStretcher && - m_timeStretcher->getTimeRatio() < 1.0 && - m_stretcherInputCount > 1 && - m_monoStretcher && !m_stretchMono) { - m_stretchMono = true; - emit audioTimeStretchMultiChannelDisabled(); - return; - } } void AudioCallbackPlaySource::setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *target) { + //!!! This should go, we should be using the ApplicationPlaybackSource callbacks + if (target == nullptr) { // reset target-related facts and figures m_deviceSampleRate = 0; @@ -628,16 +617,6 @@ } void -AudioCallbackPlaySource::setResamplerWrapper(breakfastquay::ResamplerWrapper *w) -{ - m_resamplerWrapper = w; - if (m_resamplerWrapper && m_sourceSampleRate != 0) { - m_resamplerWrapper->changeApplicationSampleRate - (int(round(m_sourceSampleRate))); - } -} - -void AudioCallbackPlaySource::setSystemPlaybackBlockSize(int size) { cout << "AudioCallbackPlaySource::setTarget: Block size -> " << size << endl; @@ -736,6 +715,7 @@ RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, rate); + /*!!! sv_frame_t stretchlat = 0; double timeRatio = 1.0; @@ -745,7 +725,7 @@ } RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, rate); - + */ // When the target has just requested a block from us, the last // sample it obtained was our buffer fill frame count minus the // amount of read space (converted back to source sample rate) @@ -784,12 +764,6 @@ RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, rate); - if (timeRatio != 1.0) { - lastretrieved_t = lastretrieved_t / timeRatio; - sincerequest_t = sincerequest_t / timeRatio; - latency_t = latency_t / timeRatio; - } - #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING cout << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved quantity: " << lastretrieved_t << endl; #endif @@ -805,7 +779,8 @@ if (m_rangeStarts.empty()) { // this code is only used in case of error in rebuildRangeLists RealTime playing_t = bufferedto_t - - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t +//!!! - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + - latency_t - lastretrieved_t - inbuffer_t + sincerequest_t; if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime; sv_frame_t frame = RealTime::realTime2Frame(playing_t, rate); @@ -831,7 +806,8 @@ RealTime playing_t = bufferedto_t; playing_t = playing_t - - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t +//!!! - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t + - latency_t - lastretrieved_t - inbuffer_t + sincerequest_t; // This rather gross little hack is used to ensure that latency @@ -850,7 +826,8 @@ // cout << "playing_t " << playing_t << " < playstart_t " // << playstart_t << endl; if (/*!!! sincerequest_t > RealTime::zeroTime && */ - m_playStartedAt + latency_t + stretchlat_t < +//!!! m_playStartedAt + latency_t + stretchlat_t < + m_playStartedAt + latency_t < RealTime::fromSeconds(currentTime)) { // cout << "but we've been playing for long enough that I think we should disregard it (it probably results from loop wrapping)" << endl; m_playStartFramePassed = true; @@ -1069,35 +1046,10 @@ void AudioCallbackPlaySource::setTimeStretch(double factor) { - m_stretchRatio = factor; + checkWrappers(); - int rate = int(getSourceSampleRate()); - if (!rate) return; // have to make our stretcher later - - if (m_timeStretcher || (factor == 1.0)) { - // stretch ratio will be set in next process call if appropriate - } else { - m_stretcherInputCount = getTargetChannelCount(); - RubberBandStretcher *stretcher = new RubberBandStretcher - (rate, - m_stretcherInputCount, - RubberBandStretcher::OptionProcessRealTime, - factor); - RubberBandStretcher *monoStretcher = new RubberBandStretcher - (rate, - 1, - RubberBandStretcher::OptionProcessRealTime, - factor); - m_stretcherInputs = new float *[m_stretcherInputCount]; - m_stretcherInputSizes = new sv_frame_t[m_stretcherInputCount]; - for (int c = 0; c < m_stretcherInputCount; ++c) { - m_stretcherInputSizes[c] = 16384; - m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; - } - m_monoStretcher = monoStretcher; - m_timeStretcher = stretcher; - } - + m_timeStretchWrapper->setTimeStretchRatio(factor); + emit activity(tr("Change time-stretch factor to %1").arg(factor)); } @@ -1202,166 +1154,51 @@ if (count == 0) return 0; - RubberBandStretcher *ts = m_timeStretcher; - RubberBandStretcher *ms = m_monoStretcher; - - double ratio = ts ? ts->getTimeRatio() : 1.0; - - if (ratio != m_stretchRatio) { - if (!ts) { - SVCERR << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << endl; - m_stretchRatio = 1.0; - } else { - ts->setTimeRatio(m_stretchRatio); - if (ms) ms->setTimeRatio(m_stretchRatio); - if (m_stretchRatio >= 1.0) m_stretchMono = false; - } - } - - int stretchChannels = m_stretcherInputCount; - if (m_stretchMono) { - if (ms) { - ts = ms; - stretchChannels = 1; - } else { - m_stretchMono = false; - } - } - if (m_target) { m_lastRetrievedBlockSize = count; m_lastRetrievalTimestamp = m_target->getCurrentTime(); } - if (!ts || ratio == 1.f) { - - int got = 0; + int got = 0; #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cout << "channels == " << channels << endl; + cout << "channels == " << channels << endl; #endif - for (int ch = 0; ch < channels; ++ch) { + for (int ch = 0; ch < channels; ++ch) { - RingBuffer<float> *rb = getReadRingBuffer(ch); + RingBuffer<float> *rb = getReadRingBuffer(ch); - if (rb) { + if (rb) { - // this is marginally more likely to leave our channels in - // sync after a processing failure than just passing "count": - sv_frame_t request = count; - if (ch > 0) request = got; + // this is marginally more likely to leave our channels in + // sync after a processing failure than just passing "count": + sv_frame_t request = count; + if (ch > 0) request = got; - got = rb->read(buffer[ch], int(request)); + got = rb->read(buffer[ch], int(request)); #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cout << "AudioCallbackPlaySource::getSamples: got " << got << " (of " << count << ") samples on channel " << ch << ", signalling for more (possibly)" << endl; + cout << "AudioCallbackPlaySource::getSamples: got " << got << " (of " << count << ") samples on channel " << ch << ", signalling for more (possibly)" << endl; #endif - } + } - for (int ch = 0; ch < channels; ++ch) { - for (int i = got; i < count; ++i) { - buffer[ch][i] = 0.0; - } + for (int ch = 0; ch < channels; ++ch) { + for (int i = got; i < count; ++i) { + buffer[ch][i] = 0.0; } } + } - applyAuditioningEffect(count, buffer); + applyAuditioningEffect(count, buffer); #ifdef DEBUG_AUDIO_PLAY_SOURCE cout << "AudioCallbackPlaySource::getSamples: awakening thread" << endl; #endif - m_condition.wakeAll(); - - return got; - } - - sv_frame_t available; - sv_frame_t fedToStretcher = 0; - int warned = 0; - - // The input block for a given output is approx output / ratio, - // but we can't predict it exactly, for an adaptive timestretcher. - - while ((available = ts->available()) < count) { - - sv_frame_t reqd = lrint(double(count - available) / ratio); - reqd = std::max(reqd, sv_frame_t(ts->getSamplesRequired())); - if (reqd == 0) reqd = 1; - - sv_frame_t got = reqd; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cout << "reqd = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << endl; -#endif - - for (int c = 0; c < channels; ++c) { - if (c >= m_stretcherInputCount) continue; - if (reqd > m_stretcherInputSizes[c]) { - if (c == 0) { - SVDEBUG << "NOTE: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << endl; - } - delete[] m_stretcherInputs[c]; - m_stretcherInputSizes[c] = reqd * 2; - m_stretcherInputs[c] = new float[m_stretcherInputSizes[c]]; - } - } - - for (int c = 0; c < channels; ++c) { - if (c >= m_stretcherInputCount) continue; - RingBuffer<float> *rb = getReadRingBuffer(c); - if (rb) { - sv_frame_t gotHere; - if (stretchChannels == 1 && c > 0) { - gotHere = rb->readAdding(m_stretcherInputs[0], int(got)); - } else { - gotHere = rb->read(m_stretcherInputs[c], int(got)); - } - if (gotHere < got) got = gotHere; - -#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - if (c == 0) { - cout << "feeding stretcher: got " << gotHere - << ", " << rb->getReadSpace() << " remain" << endl; - } -#endif - - } else { - SVCERR << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << endl; - } - } - - if (got < reqd) { - SVCERR << "WARNING: Read underrun in playback (" - << got << " < " << reqd << ")" << endl; - } - - ts->process(m_stretcherInputs, size_t(got), false); - - fedToStretcher += got; - - if (got == 0) break; - - if (ts->available() == available) { - SVCERR << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << endl; - if (++warned == 5) break; - } - } - - ts->retrieve(buffer, size_t(count)); - - v_zero_channels(buffer + stretchChannels, channels - stretchChannels, count); - - applyAuditioningEffect(count, buffer); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "AudioCallbackPlaySource::getSamples [stretched]: awakening thread" << endl; -#endif - m_condition.wakeAll(); - return count; + return got; } void
--- a/audio/AudioCallbackPlaySource.h Wed Feb 05 12:33:24 2020 +0000 +++ b/audio/AudioCallbackPlaySource.h Wed Mar 18 12:51:41 2020 +0000 @@ -36,10 +36,6 @@ #include <set> #include <map> -namespace RubberBand { - class RubberBandStretcher; -} - namespace breakfastquay { class ResamplerWrapper; } @@ -50,6 +46,7 @@ class PlayParameters; class RealTimePluginInstance; class AudioCallbackPlayTarget; +class TimeStretchWrapper; /** * AudioCallbackPlaySource manages audio data supply to callback-based @@ -60,6 +57,7 @@ */ class AudioCallbackPlaySource : public QObject, public AudioPlaySource, + //!!! to remove: public breakfastquay::ApplicationPlaybackSource { Q_OBJECT @@ -67,6 +65,15 @@ public: AudioCallbackPlaySource(ViewManagerBase *, QString clientName); virtual ~AudioCallbackPlaySource(); + + /** + * Return an ApplicationPlaybackSource interface to this class. + * The returned pointer is only borrowed, and the object continues + * to be owned by us. Caller must ensure the lifetime of the + * AudioCallbackPlaySource exceeds the scope in which the pointer + * is retained. + */ + breakfastquay::ApplicationPlaybackSource *getApplicationPlaybackSource(); /** * Add a data model to be played from. The source can mix @@ -124,11 +131,6 @@ * Set the playback target. */ virtual void setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *); - - /** - * Set the resampler wrapper, if one is in use. - */ - virtual void setResamplerWrapper(breakfastquay::ResamplerWrapper *); /** * Set the block size of the target audio device. This should be @@ -315,7 +317,6 @@ void channelCountIncreased(int count); // target channel count (see getTargetChannelCount()) void audioOverloadPluginDisabled(); - void audioTimeStretchMultiChannelDisabled(); void activity(QString); @@ -397,15 +398,6 @@ void clearRingBuffers(bool haveLock = false, int count = 0); void unifyRingBuffers(); - RubberBand::RubberBandStretcher *m_timeStretcher; - RubberBand::RubberBandStretcher *m_monoStretcher; - double m_stretchRatio; - bool m_stretchMono; - - int m_stretcherInputCount; - float **m_stretcherInputs; - sv_frame_t *m_stretcherInputSizes; - // Called from fill thread, m_playing true, mutex held // Return true if work done bool fillBuffers(); @@ -442,9 +434,10 @@ QMutex m_mutex; QWaitCondition m_condition; FillThread *m_fillThread; - breakfastquay::ResamplerWrapper *m_resamplerWrapper; // I don't own this + breakfastquay::ResamplerWrapper *m_resamplerWrapper; + TimeStretchWrapper *m_timeStretchWrapper; + void checkWrappers(); }; #endif -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/TimeStretchWrapper.cpp Wed Mar 18 12:51:41 2020 +0000 @@ -0,0 +1,228 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program 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. See the file + COPYING included with this distribution for more information. +*/ + +#include "TimeStretchWrapper.h" + +#include <rubberband/RubberBandStretcher.h> + +#include "base/Debug.h" + +using namespace RubberBand; +using namespace std; + +TimeStretchWrapper::TimeStretchWrapper(ApplicationPlaybackSource *source) : + m_source(source), + m_stretcher(nullptr), + m_timeRatio(1.0), + m_stretcherInputSize(16384), + m_channelCount(0), + m_sampleRate(0) +{ +} + +TimeStretchWrapper::~TimeStretchWrapper() +{ + delete m_stretcher; +} + +void +TimeStretchWrapper::setTimeStretchRatio(double ratio) +{ + lock_guard<mutex> guard(m_mutex); + + SVDEBUG << "TimeStretchWrapper::setTimeStretchRatio: setting ratio to " + << ratio << " (was " << m_timeRatio << ")" << endl; + + m_timeRatio = ratio; + + // Stretcher will be updated by checkStretcher() from next call to + // getSourceSamples() +} + +void +TimeStretchWrapper::reset() +{ + lock_guard<mutex> guard(m_mutex); + + if (m_stretcher) { + m_stretcher->reset(); + } +} + +int +TimeStretchWrapper::getSourceSamples(float *const *samples, + int nchannels, int nframes) +{ + checkStretcher(); + + lock_guard<mutex> guard(m_mutex); + + static int warnings = 0; + if (nchannels != m_channelCount) { + if (warnings >= 0) { + SVCERR << "WARNING: getSourceSamples called for a number of channels different from that set with setSystemPlaybackChannelCount (" + << nchannels << " vs " << m_channelCount << ")" << endl; + if (++warnings == 6) { + SVCERR << "(further warnings will be suppressed)" << endl; + warnings = -1; + } + } + return 0; + } + + if (!m_stretcher) { + return m_source->getSourceSamples(samples, nchannels, nframes); + } + + sv_frame_t available; + sv_frame_t fedToStretcher = 0; + + vector<float *> inputPtrs(m_channelCount, nullptr); + for (int i = 0; i < m_channelCount; ++i) { + inputPtrs[i] = m_inputs[i].data(); + } + + // The input block for a given output is approx output / ratio, + // but we can't predict it exactly, for an adaptive timestretcher. + + while ((available = m_stretcher->available()) < nframes) { + + sv_frame_t reqd = sv_frame_t + (ceil(double(nframes - available) / m_timeRatio)); + reqd = std::max(reqd, sv_frame_t(m_stretcher->getSamplesRequired())); + reqd = std::min(reqd, m_stretcherInputSize); + if (reqd == 0) reqd = 1; + + int got = m_source->getSourceSamples + (inputPtrs.data(), nchannels, reqd); + + if (got <= 0) { + SVCERR << "WARNING: Failed to obtain any source samples at all" + << endl; + return 0; + } + + m_stretcher->process + (inputPtrs.data(), size_t(got), false); + } + + return int(m_stretcher->retrieve(samples, nframes)); +} + +void +TimeStretchWrapper::checkStretcher() +{ + lock_guard<mutex> guard(m_mutex); + + if (m_timeRatio == 1.0 || !m_channelCount || !m_sampleRate) { + SVDEBUG << "TimeStretchWrapper::checkStretcher: m_timeRatio = " + << m_timeRatio << ", m_channelCount = " << m_channelCount + << ", m_sampleRate = " << m_sampleRate + << ", no need for stretcher" << endl; + if (m_stretcher) { + SVDEBUG << "(Deleting existing one)" << endl; + delete m_stretcher; + m_stretcher = nullptr; + } + return; + } + + if (m_stretcher) { + SVDEBUG << "TimeStretchWrapper::checkStretcher: setting stretcher ratio to " << m_timeRatio << endl; + m_stretcher->setTimeRatio(m_timeRatio); + return; + } + + SVDEBUG << "TimeStretchWrapper::checkStretcher: creating stretcher with ratio " << m_timeRatio << endl; + + m_stretcher = new RubberBandStretcher + (m_sampleRate, + m_channelCount, + RubberBandStretcher::OptionProcessRealTime, + m_timeRatio); + + m_inputs.resize(m_channelCount); + for (auto &v: m_inputs) { + v.resize(m_stretcherInputSize); + } +} + +void +TimeStretchWrapper::setSystemPlaybackChannelCount(int count) +{ + { + lock_guard<mutex> guard(m_mutex); + if (m_channelCount != count) { + delete m_stretcher; + m_stretcher = nullptr; + } + m_channelCount = count; + } + m_source->setSystemPlaybackChannelCount(count); +} + +void +TimeStretchWrapper::setSystemPlaybackSampleRate(int rate) +{ + { + lock_guard<mutex> guard(m_mutex); + if (m_sampleRate != rate) { + delete m_stretcher; + m_stretcher = nullptr; + } + m_sampleRate = rate; + } + m_source->setSystemPlaybackSampleRate(rate); +} + +std::string +TimeStretchWrapper::getClientName() const +{ + return m_source->getClientName(); +} + +int +TimeStretchWrapper::getApplicationSampleRate() const +{ + return m_source->getApplicationSampleRate(); +} + +int +TimeStretchWrapper::getApplicationChannelCount() const +{ + return m_source->getApplicationChannelCount(); +} + +void +TimeStretchWrapper::setSystemPlaybackBlockSize(int) +{ +} + +void +TimeStretchWrapper::setSystemPlaybackLatency(int latency) +{ + m_source->setSystemPlaybackLatency(latency); +} + +void +TimeStretchWrapper::setOutputLevels(float left, float right) +{ + m_source->setOutputLevels(left, right); +} + +void +TimeStretchWrapper::audioProcessingOverload() +{ + m_source->audioProcessingOverload(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/TimeStretchWrapper.h Wed Mar 18 12:51:41 2020 +0000 @@ -0,0 +1,108 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program 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. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_TIME_STRETCH_WRAPPER_H +#define SV_TIME_STRETCH_WRAPPER_H + +#include "bqaudioio/ApplicationPlaybackSource.h" + +#include "base/BaseTypes.h" + +#include <vector> +#include <mutex> + +namespace RubberBand { + class RubberBandStretcher; +} + +/** + * A breakfastquay::ApplicationPlaybackSource wrapper that implements + * time-stretching using Rubber Band. Note that the stretcher is + * bypassed entirely when a ratio of 1.0 is set; this means it's + * (almost) free to use one of these wrappers normally, but it also + * means you can't switch from 1.0 to another ratio (or back again) + * without some audible artifacts. + * + * This is real-time safe while the ratio is fixed, and may perform + * reallocations when the ratio changes. + */ +class TimeStretchWrapper : public breakfastquay::ApplicationPlaybackSource +{ +public: + /** + * Create a wrapper around the given ApplicationPlaybackSource, + * implementing another ApplicationPlaybackSource interface that + * draws from the same source data but with a time-stretcher + * optionally applied. + * + * The wrapper does not take ownership of the wrapped + * ApplicationPlaybackSource, whose lifespan must exceed that of + * this object. + */ + TimeStretchWrapper(ApplicationPlaybackSource *source); + ~TimeStretchWrapper(); + + /** + * Set a time stretch factor, i.e. playback speed, where 1.0 is + * normal speed + */ + void setTimeStretchRatio(double ratio); + + /** + * Clear stretcher buffers. + */ + void reset(); + + // These functions are passed through to the wrapped + // ApplicationPlaybackSource + + std::string getClientName() const override; + int getApplicationSampleRate() const override; + int getApplicationChannelCount() const override; + + void setSystemPlaybackBlockSize(int) override; + void setSystemPlaybackSampleRate(int) override; + void setSystemPlaybackChannelCount(int) override; + void setSystemPlaybackLatency(int) override; + + void setOutputLevels(float peakLeft, float peakRight) override; + void audioProcessingOverload() override; + + /** + * Request some samples from the wrapped + * ApplicationPlaybackSource, time-stretch if appropriate, and + * return them to the target + */ + int getSourceSamples(float *const *samples, int nchannels, int nframes) + override; + +private: + ApplicationPlaybackSource *m_source; + RubberBand::RubberBandStretcher *m_stretcher; + double m_timeRatio; + std::vector<std::vector<float>> m_inputs; + std::mutex m_mutex; + sv_frame_t m_stretcherInputSize; + int m_channelCount; + sv_samplerate_t m_sampleRate; + + void checkStretcher(); // call without m_mutex held + + TimeStretchWrapper(const TimeStretchWrapper &)=delete; + TimeStretchWrapper &operator=(const TimeStretchWrapper &)=delete; +}; + +#endif + +
--- a/files.pri Wed Feb 05 12:33:24 2020 +0000 +++ b/files.pri Wed Mar 18 12:51:41 2020 +0000 @@ -6,6 +6,7 @@ audio/ClipMixer.h \ audio/ContinuousSynth.h \ audio/PlaySpeedRangeMapper.h \ + audio/TimeStretchWrapper.h \ framework/Align.h \ framework/Document.h \ framework/MainWindowBase.h \ @@ -21,6 +22,7 @@ audio/ClipMixer.cpp \ audio/ContinuousSynth.cpp \ audio/PlaySpeedRangeMapper.cpp \ + audio/TimeStretchWrapper.cpp \ framework/Align.cpp \ framework/Document.cpp \ framework/MainWindowBase.cpp \
--- a/framework/MainWindowBase.cpp Wed Feb 05 12:33:24 2020 +0000 +++ b/framework/MainWindowBase.cpp Wed Mar 18 12:51:41 2020 +0000 @@ -82,7 +82,6 @@ #include <bqaudioio/SystemPlaybackTarget.h> #include <bqaudioio/SystemAudioIO.h> #include <bqaudioio/AudioFactory.h> -#include <bqaudioio/ResamplerWrapper.h> #include <QApplication> #include <QMessageBox> @@ -151,7 +150,6 @@ m_midiMode(midiMode), m_playSource(nullptr), m_recordTarget(nullptr), - m_resamplerWrapper(nullptr), m_playTarget(nullptr), m_audioIO(nullptr), m_oscQueue(nullptr), @@ -2568,17 +2566,14 @@ SVCERR << "createAudioIO: Preferred record device = \"" << preference.recordDevice << "\"" << endl; - if (!m_resamplerWrapper) { - m_resamplerWrapper = new breakfastquay::ResamplerWrapper(m_playSource); - m_playSource->setResamplerWrapper(m_resamplerWrapper); - } + breakfastquay::ApplicationPlaybackSource *source = + m_playSource->getApplicationPlaybackSource(); std::string errorString; if (m_audioMode == AUDIO_PLAYBACK_AND_RECORD) { m_audioIO = breakfastquay::AudioFactory:: - createCallbackIO(m_recordTarget, m_resamplerWrapper, - preference, errorString); + createCallbackIO(m_recordTarget, source, preference, errorString); if (m_audioIO) { SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl; m_audioIO->suspend(); // start in suspended state @@ -2593,8 +2588,7 @@ if (!m_audioIO) { m_playTarget = breakfastquay::AudioFactory:: - createCallbackPlayTarget(m_resamplerWrapper, - preference, errorString); + createCallbackPlayTarget(source, preference, errorString); if (m_playTarget) { SVCERR << "MainWindowBase::createAudioIO: Suspending on creation" << endl; m_playTarget->suspend(); // start in suspended state @@ -2648,7 +2642,6 @@ // First prevent this trying to call target. if (m_playSource) { m_playSource->setSystemPlaybackTarget(nullptr); - m_playSource->setResamplerWrapper(nullptr); } // Then delete the breakfastquay::System object. @@ -2656,14 +2649,8 @@ delete m_audioIO; delete m_playTarget; - // And the breakfastquay resampler wrapper. We need to - // delete/recreate this if the channel count changes, which is one - // of the use cases for recreateAudioIO() calling this - delete m_resamplerWrapper; - m_audioIO = nullptr; m_playTarget = nullptr; - m_resamplerWrapper = nullptr; } void
--- a/framework/MainWindowBase.h Wed Feb 05 12:33:24 2020 +0000 +++ b/framework/MainWindowBase.h Wed Mar 18 12:51:41 2020 +0000 @@ -50,6 +50,7 @@ class WaveformLayer; class WaveFileModel; class AudioCallbackPlaySource; +class TimeStretchWrapper; class AudioCallbackRecordTarget; class CommandHistory; class QMenu; @@ -75,7 +76,6 @@ namespace breakfastquay { class SystemPlaybackTarget; class SystemAudioIO; - class ResamplerWrapper; } /** @@ -413,7 +413,6 @@ AudioCallbackPlaySource *m_playSource; AudioCallbackRecordTarget *m_recordTarget; - breakfastquay::ResamplerWrapper *m_resamplerWrapper; breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this... breakfastquay::SystemAudioIO *m_audioIO; // ... and this exists