# HG changeset patch # User Chris Cannam # Date 1584634442 0 # Node ID ddfac001b543113b7077d386fd09d2000f81d68e # Parent 48001ed9143ba611961a6003a9e7f3c49cbb133c Introduce EffectWrapper for the auditioning effect diff -r 48001ed9143b -r ddfac001b543 audio/AudioCallbackPlaySource.cpp --- a/audio/AudioCallbackPlaySource.cpp Wed Mar 18 12:51:41 2020 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Thu Mar 19 16:14:02 2020 +0000 @@ -17,6 +17,7 @@ #include "AudioGenerator.h" #include "TimeStretchWrapper.h" +#include "EffectWrapper.h" #include "data/model/Model.h" #include "base/ViewManagerBase.h" @@ -71,14 +72,12 @@ m_outputLeft(0.0), m_outputRight(0.0), m_levelsSet(false), - m_auditioningPlugin(nullptr), - m_auditioningPluginBypassed(false), - m_auditioningPluginFailed(false), m_playStartFrame(0), m_playStartFramePassed(false), m_fillThread(nullptr), m_resamplerWrapper(nullptr), - m_timeStretchWrapper(nullptr) + m_timeStretchWrapper(nullptr), + m_auditioningEffectWrapper(nullptr) { m_viewManager->setAudioPlaySource(this); @@ -127,6 +126,10 @@ delete m_audioGenerator; + delete m_timeStretchWrapper; + delete m_auditioningEffectWrapper; + delete m_resamplerWrapper; + m_bufferScavenger.scavenge(true); m_pluginScavenger.scavenge(true); #ifdef DEBUG_AUDIO_PLAY_SOURCE @@ -155,8 +158,11 @@ if (!m_resamplerWrapper) { m_resamplerWrapper = new breakfastquay::ResamplerWrapper(this); } + if (!m_auditioningEffectWrapper) { + m_auditioningEffectWrapper = new EffectWrapper(m_resamplerWrapper); + } if (!m_timeStretchWrapper) { - m_timeStretchWrapper = new TimeStretchWrapper(m_resamplerWrapper); + m_timeStretchWrapper = new TimeStretchWrapper(m_auditioningEffectWrapper); } } @@ -595,9 +601,9 @@ if (!m_playing) return; - RealTimePluginInstance *ap = m_auditioningPlugin; - if (ap && !m_auditioningPluginBypassed) { - m_auditioningPluginBypassed = true; + if (m_auditioningEffectWrapper && + !m_auditioningEffectWrapper->isBypassed()) { + m_auditioningEffectWrapper->setBypassed(true); emit audioOverloadPluginDisabled(); return; } @@ -980,22 +986,20 @@ } void -AudioCallbackPlaySource::setAuditioningEffect(Auditionable *a) +AudioCallbackPlaySource::setAuditioningEffect(std::shared_ptr a) { - RealTimePluginInstance *plugin = dynamic_cast(a); + auto plugin = std::dynamic_pointer_cast(a); if (a && !plugin) { SVCERR << "WARNING: AudioCallbackPlaySource::setAuditioningEffect: auditionable object " << a << " is not a real-time plugin instance" << endl; } m_mutex.lock(); - m_auditioningPlugin = plugin; - m_auditioningPluginBypassed = false; - m_auditioningPluginFailed = false; + m_auditioningEffectWrapper->setEffect(plugin); + m_auditioningEffectWrapper->setBypassed(false); m_mutex.unlock(); SVDEBUG << "AudioCallbackPlaySource::setAuditioningEffect: set plugin to " - << plugin << " and bypassed to " << m_auditioningPluginBypassed - << endl; + << plugin << endl; } void @@ -1190,8 +1194,6 @@ } } - applyAuditioningEffect(count, buffer); - #ifdef DEBUG_AUDIO_PLAY_SOURCE cout << "AudioCallbackPlaySource::getSamples: awakening thread" << endl; #endif @@ -1201,68 +1203,6 @@ return got; } -void -AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float *const *buffers) -{ - if (m_auditioningPluginBypassed) return; - RealTimePluginInstance *plugin = m_auditioningPlugin; - if (!plugin) return; - - if ((int)plugin->getAudioInputCount() != getTargetChannelCount()) { - if (!m_auditioningPluginFailed) { - SVCERR << "AudioCallbackPlaySource::applyAuditioningEffect: " - << "Can't run plugin: plugin input count " - << plugin->getAudioInputCount() - << " != our channel count " << getTargetChannelCount() - << " (future errors for this plugin will be suppressed)" - << endl; - m_auditioningPluginFailed = true; - } - return; - } - if ((int)plugin->getAudioOutputCount() != getTargetChannelCount()) { - if (!m_auditioningPluginFailed) { - SVCERR << "AudioCallbackPlaySource::applyAuditioningEffect: " - << "Can't run plugin: plugin output count " - << plugin->getAudioOutputCount() - << " != our channel count " << getTargetChannelCount() - << " (future errors for this plugin will be suppressed)" - << endl; - m_auditioningPluginFailed = true; - } - return; - } - if ((int)plugin->getBufferSize() < count) { - if (!m_auditioningPluginFailed) { - SVCERR << "AudioCallbackPlaySource::applyAuditioningEffect: " - << "Can't run plugin: plugin buffer size " - << plugin->getBufferSize() - << " < our block size " << count - << " (future errors for this plugin will be suppressed)" - << endl; - m_auditioningPluginFailed = true; - } - return; - } - - float **ib = plugin->getAudioInputBuffers(); - float **ob = plugin->getAudioOutputBuffers(); - - for (int c = 0; c < getTargetChannelCount(); ++c) { - for (int i = 0; i < count; ++i) { - ib[c][i] = buffers[c][i]; - } - } - - plugin->run(Vamp::RealTime::zeroTime, int(count)); - - for (int c = 0; c < getTargetChannelCount(); ++c) { - for (int i = 0; i < count; ++i) { - buffers[c][i] = ob[c][i]; - } - } -} - // Called from fill thread, m_playing true, mutex held bool AudioCallbackPlaySource::fillBuffers() diff -r 48001ed9143b -r ddfac001b543 audio/AudioCallbackPlaySource.h --- a/audio/AudioCallbackPlaySource.h Wed Mar 18 12:51:41 2020 +0000 +++ b/audio/AudioCallbackPlaySource.h Thu Mar 19 16:14:02 2020 +0000 @@ -47,6 +47,7 @@ class RealTimePluginInstance; class AudioCallbackPlayTarget; class TimeStretchWrapper; +class EffectWrapper; /** * AudioCallbackPlaySource manages audio data supply to callback-based @@ -290,7 +291,8 @@ * Pass a null pointer to remove the current auditioning plugin, * if any. */ - virtual void setAuditioningEffect(Auditionable *plugin) override; + virtual void setAuditioningEffect(std::shared_ptr plugin) + override; /** * Specify that only the given set of models should be played. @@ -370,9 +372,6 @@ float m_outputLeft; float m_outputRight; bool m_levelsSet; - RealTimePluginInstance *m_auditioningPlugin; - bool m_auditioningPluginBypassed; - bool m_auditioningPluginFailed; Scavenger m_pluginScavenger; sv_frame_t m_playStartFrame; bool m_playStartFramePassed; @@ -408,9 +407,6 @@ // frame argument passed in, in the case of looping). sv_frame_t mixModels(sv_frame_t &frame, sv_frame_t count, float **buffers); - // Called from getSourceSamples. - void applyAuditioningEffect(sv_frame_t count, float *const *buffers); - // Ranges of current selections, if play selection is active std::vector m_rangeStarts; std::vector m_rangeDurations; @@ -436,6 +432,7 @@ FillThread *m_fillThread; breakfastquay::ResamplerWrapper *m_resamplerWrapper; TimeStretchWrapper *m_timeStretchWrapper; + EffectWrapper *m_auditioningEffectWrapper; void checkWrappers(); }; diff -r 48001ed9143b -r ddfac001b543 audio/EffectWrapper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/EffectWrapper.cpp Thu Mar 19 16:14:02 2020 +0000 @@ -0,0 +1,216 @@ +/* -*- 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 "EffectWrapper.h" + +#include + +#include "base/Debug.h" + +using namespace std; + +static const int DEFAULT_RING_BUFFER_SIZE = 131071; + +EffectWrapper::EffectWrapper(ApplicationPlaybackSource *source) : + m_source(source), + m_bypassed(false), + m_failed(false), + m_channelCount(0) +{ +} + +EffectWrapper::~EffectWrapper() +{ +} + +void +EffectWrapper::setEffect(weak_ptr effect) +{ + lock_guard guard(m_mutex); + + m_effect = effect; + m_failed = false; +} + +void +EffectWrapper::setBypassed(bool bypassed) +{ + lock_guard guard(m_mutex); + + m_bypassed = bypassed; +} + +bool +EffectWrapper::isBypassed() const +{ + lock_guard guard(m_mutex); + + return m_bypassed; +} + +void +EffectWrapper::reset() +{ + lock_guard guard(m_mutex); + + for (auto &rb: m_effectOutputBuffers) { + rb.reset(); + } +} + +int +EffectWrapper::getSourceSamples(float *const *samples, + int nchannels, int nframes) +{ + lock_guard guard(m_mutex); + + auto effect(m_effect.lock()); + + if (!effect || m_bypassed || m_failed) { + return m_source->getSourceSamples(samples, nchannels, nframes); + } + + 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 ((int)effect->getAudioInputCount() != m_channelCount) { + if (!m_failed) { + SVCERR << "EffectWrapper::getSourceSamples: " + << "Can't run plugin: plugin input count " + << effect->getAudioInputCount() + << " != our channel count " << m_channelCount + << " (future errors for this plugin will be suppressed)" + << endl; + m_failed = true; + } + } + if ((int)effect->getAudioOutputCount() != m_channelCount) { + if (!m_failed) { + SVCERR << "EffectWrapper::getSourceSamples: " + << "Can't run plugin: plugin output count " + << effect->getAudioOutputCount() + << " != our channel count " << m_channelCount + << " (future errors for this plugin will be suppressed)" + << endl; + m_failed = true; + } + } + + if (m_failed) { + return m_source->getSourceSamples(samples, nchannels, nframes); + } + + float **ib = effect->getAudioInputBuffers(); + float **ob = effect->getAudioOutputBuffers(); + int blockSize = effect->getBufferSize(); + + int got = 0; + int offset = 0; + + while (got < nframes) { + + int read = 0; + for (int c = 0; c < nchannels; ++c) { + read = m_effectOutputBuffers[c].read(samples[c], nframes - got); + } + + got += read; + + if (got < nframes) { + + int toRun = m_source->getSourceSamples(ib, nchannels, blockSize); + if (toRun <= 0) break; + + effect->run(Vamp::RealTime::zeroTime, toRun); + + for (int c = 0; c < nchannels; ++c) { + m_effectOutputBuffers[c].write(ob[c], toRun); + } + } + } + + return got; +} + +void +EffectWrapper::setSystemPlaybackChannelCount(int count) +{ + { + lock_guard guard(m_mutex); + m_effectOutputBuffers.resize + (count, RingBuffer(DEFAULT_RING_BUFFER_SIZE)); + m_channelCount = count; + } + m_source->setSystemPlaybackChannelCount(count); +} + +void +EffectWrapper::setSystemPlaybackSampleRate(int rate) +{ + m_source->setSystemPlaybackSampleRate(rate); +} + +std::string +EffectWrapper::getClientName() const +{ + return m_source->getClientName(); +} + +int +EffectWrapper::getApplicationSampleRate() const +{ + return m_source->getApplicationSampleRate(); +} + +int +EffectWrapper::getApplicationChannelCount() const +{ + return m_source->getApplicationChannelCount(); +} + +void +EffectWrapper::setSystemPlaybackBlockSize(int sz) +{ + SVDEBUG << "NOTE: EffectWrapper::setSystemPlaybackBlockSize called " + << "with size = " << sz << "; not passing to wrapped source, as " + << "actual block size will vary" << endl; +} + +void +EffectWrapper::setSystemPlaybackLatency(int latency) +{ + m_source->setSystemPlaybackLatency(latency); +} + +void +EffectWrapper::setOutputLevels(float left, float right) +{ + m_source->setOutputLevels(left, right); +} + +void +EffectWrapper::audioProcessingOverload() +{ + m_source->audioProcessingOverload(); +} diff -r 48001ed9143b -r ddfac001b543 audio/EffectWrapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audio/EffectWrapper.h Thu Mar 19 16:14:02 2020 +0000 @@ -0,0 +1,115 @@ +/* -*- 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_EFFECT_WRAPPER_H +#define SV_EFFECT_WRAPPER_H + +#include "bqaudioio/ApplicationPlaybackSource.h" + +#include "base/BaseTypes.h" +#include "base/RingBuffer.h" + +#include "plugin/RealTimePluginInstance.h" + +#include +#include +#include + +/** + * A breakfastquay::ApplicationPlaybackSource wrapper that applies a + * real-time effect plugin. + */ +class EffectWrapper : public breakfastquay::ApplicationPlaybackSource +{ +public: + /** + * Create a wrapper around the given ApplicationPlaybackSource, + * implementing another ApplicationPlaybackSource interface that + * draws from the same source data but with an effect optionally + * applied. + * + * The wrapper does not take ownership of the wrapped + * ApplicationPlaybackSource, whose lifespan must exceed that of + * this object. + */ + EffectWrapper(ApplicationPlaybackSource *source); + ~EffectWrapper(); + + /** + * Set the effect to apply. The effect instance is shared with the + * caller: the expectation is that the caller may continue to + * modify its parameters etc during auditioning. Replaces any + * instance previously set. + */ + void setEffect(std::weak_ptr); + + /** + * Remove any applied effect without setting another one. + */ + void clearEffect(); + + /** + * Bypass or un-bypass the effect. + */ + void setBypassed(bool bypassed); + + /** + * Return true if the effect is bypassed. + */ + bool isBypassed() const; + + /** + * Clear any buffered data. + */ + 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, apply effect if set, and return them + * to the target + */ + int getSourceSamples(float *const *samples, int nchannels, int nframes) + override; + +private: + ApplicationPlaybackSource *m_source; + std::weak_ptr m_effect; + bool m_bypassed; + bool m_failed; + int m_channelCount; + std::vector> m_effectOutputBuffers; + mutable std::mutex m_mutex; + + EffectWrapper(const EffectWrapper &)=delete; + EffectWrapper &operator=(const EffectWrapper &)=delete; +}; + +#endif + + diff -r 48001ed9143b -r ddfac001b543 audio/TimeStretchWrapper.cpp --- a/audio/TimeStretchWrapper.cpp Wed Mar 18 12:51:41 2020 +0000 +++ b/audio/TimeStretchWrapper.cpp Thu Mar 19 16:14:02 2020 +0000 @@ -205,8 +205,11 @@ } void -TimeStretchWrapper::setSystemPlaybackBlockSize(int) +TimeStretchWrapper::setSystemPlaybackBlockSize(int sz) { + SVDEBUG << "NOTE: TimeStretchWrapper::setSystemPlaybackBlockSize called " + << "with size = " << sz << "; not passing to wrapped source, as " + << "actual block size will vary" << endl; } void diff -r 48001ed9143b -r ddfac001b543 files.pri --- a/files.pri Wed Mar 18 12:51:41 2020 +0000 +++ b/files.pri Thu Mar 19 16:14:02 2020 +0000 @@ -5,6 +5,7 @@ audio/AudioGenerator.h \ audio/ClipMixer.h \ audio/ContinuousSynth.h \ + audio/EffectWrapper.h \ audio/PlaySpeedRangeMapper.h \ audio/TimeStretchWrapper.h \ framework/Align.h \ @@ -21,6 +22,7 @@ audio/AudioGenerator.cpp \ audio/ClipMixer.cpp \ audio/ContinuousSynth.cpp \ + audio/EffectWrapper.cpp \ audio/PlaySpeedRangeMapper.cpp \ audio/TimeStretchWrapper.cpp \ framework/Align.cpp \ diff -r 48001ed9143b -r ddfac001b543 framework/TransformUserConfigurator.cpp --- a/framework/TransformUserConfigurator.cpp Wed Mar 18 12:51:41 2020 +0000 +++ b/framework/TransformUserConfigurator.cpp Thu Mar 19 16:14:02 2020 +0000 @@ -109,7 +109,11 @@ if (effect && source) { SVDEBUG << "Setting auditioning effect" << endl; - source->setAuditioningEffect(rtp); + //!!! This requires a shared_ptr, but we don't manage our + //!!! plugin using shared_ptrs yet. Do this as a stopgap. + std::shared_ptr auditionable + (std::make_shared(true), rtp); + source->setAuditioningEffect(auditionable); } } else { @@ -229,7 +233,7 @@ delete dialog; if (effect && source) { - source->setAuditioningEffect(nullptr); + source->setAuditioningEffect({}); } return ok;