changeset 559:7b115a6505b8 3.0-integration

Handle increases in the overall channel count by closing and reopening the audio device.
author Chris Cannam
date Tue, 13 Dec 2016 12:03:48 +0000
parents 206d65e2b69a
children e348e0c52115
files audio/AudioCallbackPlaySource.cpp audio/AudioCallbackPlaySource.h audio/AudioRecordTarget.cpp audio/AudioRecordTarget.h framework/MainWindowBase.cpp
diffstat 5 files changed, 148 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp	Mon Dec 12 17:15:24 2016 +0000
+++ b/audio/AudioCallbackPlaySource.cpp	Tue Dec 13 12:03:48 2016 +0000
@@ -30,9 +30,13 @@
 #include "bqaudioio/SystemPlaybackTarget.h"
 #include "bqaudioio/ResamplerWrapper.h"
 
+#include "bqvec/VectorOps.h"
+
 #include <rubberband/RubberBandStretcher.h>
 using namespace RubberBand;
 
+using breakfastquay::v_zero_channels;
+
 #include <iostream>
 #include <cassert>
 
@@ -55,6 +59,7 @@
     m_blockSize(1024),
     m_sourceSampleRate(0),
     m_deviceSampleRate(0),
+    m_deviceChannelCount(0),
     m_playLatency(0),
     m_target(0),
     m_lastRetrievalTimestamp(0.0),
@@ -158,7 +163,7 @@
 	m_lastModelEndFrame = model->getEndFrame();
     }
 
-    bool buffersChanged = false, srChanged = false;
+    bool buffersIncreased = false, srChanged = false;
 
     int modelChannels = 1;
     ReadOnlyWaveFileModel *rowfm = qobject_cast<ReadOnlyWaveFileModel *>(model);
@@ -225,7 +230,7 @@
 
     if (!m_writeBuffers || (int)m_writeBuffers->size() < getTargetChannelCount()) {
 	clearRingBuffers(true, getTargetChannelCount());
-	buffersChanged = true;
+	buffersIncreased = true;
     } else {
 	if (willPlay) clearRingBuffers(true);
     }
@@ -256,17 +261,20 @@
 
     m_mutex.unlock();
 
-    //!!!
-    
     m_audioGenerator->setTargetChannelCount(getTargetChannelCount());
 
+    if (buffersIncreased) {
+        SVDEBUG << "AudioCallbackPlaySource::addModel: Number of buffers increased, signalling channelCountIncreased" << endl;
+        emit channelCountIncreased();
+    }
+    
     if (!m_fillThread) {
 	m_fillThread = new FillThread(*this);
 	m_fillThread->start();
     }
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
-    cout << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl;
+    SVDEBUG << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl;
 #endif
 
     connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
@@ -275,7 +283,7 @@
 #ifdef DEBUG_AUDIO_PLAY_SOURCE
     cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl;
 #endif
-
+    
     m_condition.wakeAll();
 }
 
@@ -580,6 +588,11 @@
 void
 AudioCallbackPlaySource::setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *target)
 {
+    if (target == 0) {
+        // reset target-related facts and figures
+        m_deviceSampleRate = 0;
+        m_deviceChannelCount = 0;
+    }
     m_target = target;
 }
 
@@ -948,8 +961,9 @@
 }
 
 void
-AudioCallbackPlaySource::setSystemPlaybackChannelCount(int)
+AudioCallbackPlaySource::setSystemPlaybackChannelCount(int count)
 {
+    m_deviceChannelCount = count;
 }
 
 void
@@ -999,6 +1013,12 @@
     return m_sourceChannelCount;
 }
 
+int
+AudioCallbackPlaySource::getDeviceChannelCount() const
+{
+    return m_deviceChannelCount;
+}
+
 sv_samplerate_t
 AudioCallbackPlaySource::getSourceSampleRate() const
 {
@@ -1041,19 +1061,70 @@
 }
 
 int
-AudioCallbackPlaySource::getSourceSamples(int count, float **buffer)
+AudioCallbackPlaySource::getSourceSamples(float *const *buffer,
+                                          int requestedChannels,
+                                          int count)
 {
+    // In principle, the target will handle channel mapping in cases
+    // where our channel count differs from the device's. But that
+    // only holds if our channel count doesn't change -- i.e. if
+    // getApplicationChannelCount() always returns the same value as
+    // it did when the target was created, and if this function always
+    // returns that number of channels.
+    //
+    // Unfortunately that can't hold for us -- we always have at least
+    // 2 channels but if the user opens a new main model with more
+    // channels than that (and more than the last main model) then our
+    // target channel count necessarily gets increased.
+    //
+    // We have:
+    // 
+    // getSourceChannelCount() -> number of channels available to
+    // provide from real model data
+    //
+    // getTargetChannelCount() -> number we will actually provide;
+    // same as getSourceChannelCount() except that it is always at
+    // least 2
+    //
+    // getDeviceChannelCount() -> number the device will emit, usually
+    // equal to the value of getTargetChannelCount() at the time the
+    // device was initialised, unless the device could not provide
+    // that number
+    //
+    // requestedChannels -> number the device is expecting from us,
+    // always equal to the value of getTargetChannelCount() at the
+    // time the device was initialised
+    //
+    // If the requested channel count is at least the target channel
+    // count, then we go ahead and provide the target channels as
+    // expected. We just zero any spare channels.
+    //
+    // If the requested channel count is smaller than the target
+    // channel count, then we don't know what to do and we provide
+    // nothing. This shouldn't happen as long as management is on the
+    // ball -- we emit channelCountIncreased() when the target channel
+    // count increases, and whatever code "owns" the driver should
+    // have reopened the audio device when it got that signal. But
+    // there's a race condition there, which we accommodate with this
+    // check.
+
+    int channels = getTargetChannelCount();
+
     if (!m_playing) {
 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING
         SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not playing" << endl;
 #endif
-	for (int ch = 0; ch < getTargetChannelCount(); ++ch) {
-	    for (int i = 0; i < count; ++i) {
-		buffer[ch][i] = 0.0;
-	    }
-	}
+        v_zero_channels(buffer, requestedChannels, count);
 	return 0;
     }
+    if (requestedChannels < channels) {
+        SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Not enough device channels (" << requestedChannels << ", need " << channels << "); hoping device is about to be reopened" << endl;
+        v_zero_channels(buffer, requestedChannels, count);
+        return 0;
+    }
+    if (requestedChannels > channels) {
+        v_zero_channels(buffer + channels, requestedChannels - channels, count);
+    }
 
 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING
     SVDEBUG << "AudioCallbackPlaySource::getSourceSamples: Playing" << endl;
@@ -1062,7 +1133,7 @@
     // Ensure that all buffers have at least the amount of data we
     // need -- else reduce the size of our requests correspondingly
 
-    for (int ch = 0; ch < getTargetChannelCount(); ++ch) {
+    for (int ch = 0; ch < channels; ++ch) {
 
         RingBuffer<float> *rb = getReadRingBuffer(ch);
         
@@ -1125,9 +1196,9 @@
 
 	int got = 0;
 
-        cerr << "getTargetChannelCount() == " << getTargetChannelCount() << endl;
+        cerr << "channels == " << channels << endl;
         
-	for (int ch = 0; ch < getTargetChannelCount(); ++ch) {
+	for (int ch = 0; ch < channels; ++ch) {
 
 	    RingBuffer<float> *rb = getReadRingBuffer(ch);
 
@@ -1145,7 +1216,7 @@
 #endif
 	    }
 
-	    for (int ch = 0; ch < getTargetChannelCount(); ++ch) {
+	    for (int ch = 0; ch < channels; ++ch) {
 		for (int i = got; i < count; ++i) {
 		    buffer[ch][i] = 0.0;
 		}
@@ -1163,7 +1234,6 @@
 	return got;
     }
 
-    int channels = getTargetChannelCount();
     sv_frame_t available;
     sv_frame_t fedToStretcher = 0;
     int warned = 0;
@@ -1238,11 +1308,7 @@
 
     ts->retrieve(buffer, size_t(count));
 
-    for (int c = stretchChannels; c < getTargetChannelCount(); ++c) {
-        for (int i = 0; i < count; ++i) {
-            buffer[c][i] = buffer[0][i];
-        }
-    }
+    v_zero_channels(buffer + stretchChannels, channels - stretchChannels, count);
 
     applyAuditioningEffect(count, buffer);
 
@@ -1256,7 +1322,7 @@
 }
 
 void
-AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float **buffers)
+AudioCallbackPlaySource::applyAuditioningEffect(sv_frame_t count, float *const *buffers)
 {
     if (m_auditioningPluginBypassed) return;
     RealTimePluginInstance *plugin = m_auditioningPlugin;
--- a/audio/AudioCallbackPlaySource.h	Mon Dec 12 17:15:24 2016 +0000
+++ b/audio/AudioCallbackPlaySource.h	Tue Dec 13 12:03:48 2016 +0000
@@ -90,23 +90,23 @@
      * from the given frame.  If playback is already under way, reseek
      * to the given frame and continue.
      */
-    virtual void play(sv_frame_t startFrame);
+    virtual void play(sv_frame_t startFrame) override;
 
     /**
      * Stop playback and ensure that no more data is returned.
      */
-    virtual void stop();
+    virtual void stop() override;
 
     /**
      * Return whether playback is currently supposed to be happening.
      */
-    virtual bool isPlaying() const { return m_playing; }
+    virtual bool isPlaying() const override { return m_playing; }
 
     /**
      * Return the frame number that is currently expected to be coming
      * out of the speakers.  (i.e. compensating for playback latency.)
      */
-    virtual sv_frame_t getCurrentPlayingFrame();
+    virtual sv_frame_t getCurrentPlayingFrame() override;
     
     /** 
      * Return the last frame that would come out of the speakers if we
@@ -133,7 +133,7 @@
      * Set the block size of the target audio device.  This should be
      * called by the target class.
      */
-    virtual void setSystemPlaybackBlockSize(int blockSize);
+    virtual void setSystemPlaybackBlockSize(int blockSize) override;
 
     /**
      * Get the block size of the target audio device.  This may be an
@@ -150,7 +150,7 @@
      * highest last frame across all channels) requested via
      * getSamples().  The default is zero.
      */
-    void setSystemPlaybackLatency(int);
+    virtual void setSystemPlaybackLatency(int) override;
 
     /**
      * Get the playback latency of the target audio device.
@@ -164,7 +164,7 @@
      * source sample rate, this class will resample automatically to
      * fit.
      */
-    void setSystemPlaybackSampleRate(int);
+    virtual void setSystemPlaybackSampleRate(int) override;
 
     /**
      * Return the sample rate set by the target audio device (or the
@@ -175,9 +175,11 @@
     /**
      * Indicate how many channels the target audio device was opened
      * with. Note that the target device does channel mixing in the
-     * case where our requested channel count does not match its.
+     * case where our requested channel count does not match its, so
+     * long as we provide the number of channels we specified when the
+     * target was started in getApplicationChannelCount().
      */
-    void setSystemPlaybackChannelCount(int);
+    virtual void setSystemPlaybackChannelCount(int) override;
     
     /**
      * Set the current output levels for metering (for call from the
@@ -212,6 +214,13 @@
     virtual int getTargetChannelCount() const override;
 
     /**
+     * Get the number of channels of audio the device is
+     * expecting. Equal to whatever getTargetChannelCount() was
+     * returning at the time the device was initialised.
+     */
+    int getDeviceChannelCount() const;
+    
+    /**
      * ApplicationPlaybackSource equivalent of the above.
      *
      * override from breakfastquay::ApplicationPlaybackSource
@@ -249,7 +258,7 @@
      * audio data, in all channels.  This may safely be called from a
      * realtime thread.
      */
-    virtual int getSourceSamples(int count, float **buffer);
+    virtual int getSourceSamples(float *const *buffer, int nchannels, int count) override;
 
     /**
      * Set the time stretcher factor (i.e. playback speed).
@@ -293,6 +302,8 @@
                             sv_samplerate_t available,
                             bool willResample);
 
+    void channelCountIncreased();
+
     void audioOverloadPluginDisabled();
     void audioTimeStretchMultiChannelDisabled();
 
@@ -334,6 +345,7 @@
     sv_frame_t                        m_blockSize;
     sv_samplerate_t                   m_sourceSampleRate;
     sv_samplerate_t                   m_deviceSampleRate;
+    int                               m_deviceChannelCount;
     sv_frame_t                        m_playLatency;
     breakfastquay::SystemPlaybackTarget *m_target;
     double                            m_lastRetrievalTimestamp;
@@ -393,7 +405,7 @@
     sv_frame_t mixModels(sv_frame_t &frame, sv_frame_t count, float **buffers);
 
     // Called from getSourceSamples.
-    void applyAuditioningEffect(sv_frame_t count, float **buffers);
+    void applyAuditioningEffect(sv_frame_t count, float *const *buffers);
 
     // Ranges of current selections, if play selection is active
     std::vector<RealTime> m_rangeStarts;
--- a/audio/AudioRecordTarget.cpp	Mon Dec 12 17:15:24 2016 +0000
+++ b/audio/AudioRecordTarget.cpp	Tue Dec 13 12:03:48 2016 +0000
@@ -38,6 +38,18 @@
     QMutexLocker locker(&m_mutex);
 }
 
+int
+AudioRecordTarget::getApplicationSampleRate() const
+{
+    return 0; // don't care
+}
+
+int
+AudioRecordTarget::getApplicationChannelCount() const
+{
+    return m_recordChannelCount;
+}
+
 void
 AudioRecordTarget::setSystemRecordBlockSize(int)
 {
@@ -61,7 +73,7 @@
 }
 
 void
-AudioRecordTarget::putSamples(int nframes, float **samples)
+AudioRecordTarget::putSamples(const float *const *samples, int nchannels, int nframes)
 {
     bool secChanged = false;
     sv_frame_t frameToEmit = 0;
--- a/audio/AudioRecordTarget.h	Mon Dec 12 17:15:24 2016 +0000
+++ b/audio/AudioRecordTarget.h	Tue Dec 13 12:03:48 2016 +0000
@@ -36,21 +36,21 @@
     AudioRecordTarget(ViewManagerBase *, QString clientName);
     virtual ~AudioRecordTarget();
 
-    virtual std::string getClientName() const { return m_clientName; }
+    virtual std::string getClientName() const override { return m_clientName; }
     
-    virtual int getApplicationSampleRate() const { return 0; } // don't care
-    virtual int getApplicationChannelCount() const { return 2; }
+    virtual int getApplicationSampleRate() const override;
+    virtual int getApplicationChannelCount() const override;
 
-    virtual void setSystemRecordBlockSize(int);
-    virtual void setSystemRecordSampleRate(int);
-    virtual void setSystemRecordLatency(int);
-    virtual void setSystemRecordChannelCount(int);
+    virtual void setSystemRecordBlockSize(int) override;
+    virtual void setSystemRecordSampleRate(int) override;
+    virtual void setSystemRecordLatency(int) override;
+    virtual void setSystemRecordChannelCount(int) override;
 
-    virtual void putSamples(int nframes, float **samples);
+    virtual void putSamples(const float *const *samples, int nchannels, int nframes) override;
     
-    virtual void setInputLevels(float peakLeft, float peakRight);
+    virtual void setInputLevels(float peakLeft, float peakRight) override;
 
-    virtual void audioProcessingOverload() { }
+    virtual void audioProcessingOverload() override { }
 
     QString getRecordContainerFolder();
     QString getRecordFolder();
--- a/framework/MainWindowBase.cpp	Mon Dec 12 17:15:24 2016 +0000
+++ b/framework/MainWindowBase.cpp	Tue Dec 13 12:03:48 2016 +0000
@@ -242,6 +242,8 @@
 
     connect(m_playSource, SIGNAL(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)),
 	    this,           SLOT(sampleRateMismatch(sv_samplerate_t, sv_samplerate_t, bool)));
+    connect(m_playSource, SIGNAL(channelCountIncreased()),
+            this,           SLOT(recreateAudioIO()));
     connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()),
             this,           SLOT(audioOverloadPluginDisabled()));
     connect(m_playSource, SIGNAL(audioTimeStretchMultiChannelDisabled()),
@@ -299,7 +301,6 @@
     deleteAudioIO();
 
     // Then delete the Application objects.
-    delete m_resamplerWrapper;
     delete m_playSource;
     delete m_recordTarget;
     
@@ -2373,15 +2374,24 @@
 MainWindowBase::deleteAudioIO()
 {
     // First prevent this trying to call target.
-    if (m_playSource) m_playSource->setSystemPlaybackTarget(0);
+    if (m_playSource) {
+        m_playSource->setSystemPlaybackTarget(0);
+        m_playSource->setResamplerWrapper(0);
+    }
 
     // Then delete the breakfastquay::System object.
     // Only one of these two exists!
     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 = 0;
     m_playTarget = 0;
+    m_resamplerWrapper = 0;
 }
 
 void