changeset 739:ddfac001b543 audio-source-refactor

Introduce EffectWrapper for the auditioning effect
author Chris Cannam
date Thu, 19 Mar 2020 16:14:02 +0000
parents 48001ed9143b
children 846970dbef17
files audio/AudioCallbackPlaySource.cpp audio/AudioCallbackPlaySource.h audio/EffectWrapper.cpp audio/EffectWrapper.h audio/TimeStretchWrapper.cpp files.pri framework/TransformUserConfigurator.cpp
diffstat 7 files changed, 366 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- 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<Auditionable> a)
 {
-    RealTimePluginInstance *plugin = dynamic_cast<RealTimePluginInstance *>(a);
+    auto plugin = std::dynamic_pointer_cast<RealTimePluginInstance>(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()
--- 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<Auditionable> 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<RealTimePluginInstance> 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<RealTime> m_rangeStarts;
     std::vector<RealTime> m_rangeDurations;
@@ -436,6 +432,7 @@
     FillThread *m_fillThread;
     breakfastquay::ResamplerWrapper *m_resamplerWrapper;
     TimeStretchWrapper *m_timeStretchWrapper;
+    EffectWrapper *m_auditioningEffectWrapper;
     void checkWrappers();
 };
 
--- /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 <rubberband/RubberBandStretcher.h>
+
+#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<RealTimePluginInstance> effect)
+{
+    lock_guard<mutex> guard(m_mutex);
+
+    m_effect = effect;
+    m_failed = false;
+}
+
+void
+EffectWrapper::setBypassed(bool bypassed)
+{
+    lock_guard<mutex> guard(m_mutex);
+
+    m_bypassed = bypassed;
+}
+
+bool
+EffectWrapper::isBypassed() const
+{
+    lock_guard<mutex> guard(m_mutex);
+
+    return m_bypassed;
+}
+
+void
+EffectWrapper::reset()
+{
+    lock_guard<mutex> guard(m_mutex);
+
+    for (auto &rb: m_effectOutputBuffers) {
+        rb.reset();
+    }
+}
+
+int
+EffectWrapper::getSourceSamples(float *const *samples,
+                                int nchannels, int nframes)
+{
+    lock_guard<mutex> 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<mutex> guard(m_mutex);
+        m_effectOutputBuffers.resize
+            (count, RingBuffer<float>(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();
+}
--- /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 <vector>
+#include <mutex>
+#include <memory>
+
+/**
+ * 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<RealTimePluginInstance>);
+
+    /**
+     * 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<RealTimePluginInstance> m_effect;
+    bool m_bypassed;
+    bool m_failed;
+    int m_channelCount;
+    std::vector<RingBuffer<float>> m_effectOutputBuffers;
+    mutable std::mutex m_mutex;
+
+    EffectWrapper(const EffectWrapper &)=delete;
+    EffectWrapper &operator=(const EffectWrapper &)=delete;
+};
+
+#endif
+
+    
--- 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
--- 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 \
--- 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> auditionable
+                (std::make_shared<bool>(true), rtp);
+            source->setAuditioningEffect(auditionable);
         }
 
     } else {
@@ -229,7 +233,7 @@
     delete dialog;
 
     if (effect && source) {
-        source->setAuditioningEffect(nullptr);
+        source->setAuditioningEffect({});
     }
 
     return ok;