changeset 554:4be200469a9c 3.0-integration

Merge from branch bqresample
author Chris Cannam
date Fri, 09 Dec 2016 18:01:55 +0000
parents 4de547a5905c (current diff) 2a1e9e017484 (diff)
children 2683a8ca36ea
files
diffstat 4 files changed, 150 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/audio/AudioCallbackPlaySource.cpp	Tue Dec 06 15:59:06 2016 +0000
+++ b/audio/AudioCallbackPlaySource.cpp	Fri Dec 09 18:01:55 2016 +0000
@@ -28,6 +28,7 @@
 #include "plugin/RealTimePluginInstance.h"
 
 #include "bqaudioio/SystemPlaybackTarget.h"
+#include "bqaudioio/ResamplerWrapper.h"
 
 #include <rubberband/RubberBandStretcher.h>
 using namespace RubberBand;
@@ -53,7 +54,7 @@
     m_sourceChannelCount(0),
     m_blockSize(1024),
     m_sourceSampleRate(0),
-    m_targetSampleRate(0),
+    m_deviceSampleRate(0),
     m_playLatency(0),
     m_target(0),
     m_lastRetrievalTimestamp(0.0),
@@ -77,7 +78,8 @@
     m_stretcherInputCount(0),
     m_stretcherInputs(0),
     m_stretcherInputSizes(0),
-    m_fillThread(0)
+    m_fillThread(0),
+    m_resamplerWrapper(0)
 {
     m_viewManager->setAudioPlaySource(this);
 
@@ -228,13 +230,26 @@
 	if (willPlay) clearRingBuffers(true);
     }
 
-    if (buffersChanged || srChanged) {
+    if (srChanged) {
 
-        // There are more channels than there were before, or the
-        // source sample rate has changed
+        SVCERR << "AudioCallbackPlaySource: Source rate changed" << endl;
 
-        //!!!
+        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 = 0;
+        m_monoStretcher = 0;
+        
+        if (m_stretchRatio != 1.f) {
+            setTimeStretch(m_stretchRatio);
+        }
     }
 
     rebuildRangeLists();
@@ -538,7 +553,7 @@
 }
 
 void
-AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n)
+AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName )
 {
 }
 
@@ -573,6 +588,16 @@
 }
 
 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;
@@ -618,12 +643,12 @@
     // This method attempts to estimate which audio sample frame is
     // "currently coming through the speakers".
 
-    sv_samplerate_t targetRate = getTargetSampleRate();
+    sv_samplerate_t deviceRate = getDeviceSampleRate();
     sv_frame_t latency = m_playLatency; // at target rate
     RealTime latency_t = RealTime::zeroTime;
 
-    if (targetRate != 0) {
-        latency_t = RealTime::frame2RealTime(latency, targetRate);
+    if (deviceRate != 0) {
+        latency_t = RealTime::frame2RealTime(latency, deviceRate);
     }
 
     return getCurrentFrame(latency_t);
@@ -638,16 +663,18 @@
 sv_frame_t
 AudioCallbackPlaySource::getCurrentFrame(RealTime latency_t)
 {
-    // We resample when filling the ring buffer, and time-stretch when
-    // draining it.  The buffer contains data at the "target rate" and
-    // the latency provided by the target is also at the target rate.
-    // Because of the multiple rates involved, we do the actual
-    // calculation using RealTime instead.
+    // The ring buffers contain data at the source sample rate and all
+    // processing (including time stretching) happens at this
+    // rate. Resampling only happens after the audio data leaves this
+    // class.
+    
+    // (But because historically more than one sample rate could have
+    // been involved here, we do latency calculations using RealTime
+    // values instead of samples.)
 
-    sv_samplerate_t sourceRate = getSourceSampleRate();
-    sv_samplerate_t targetRate = getTargetSampleRate();
+    sv_samplerate_t rate = getSourceSampleRate();
 
-    if (sourceRate == 0 || targetRate == 0) return 0;
+    if (rate == 0) return 0;
 
     int inbuffer = 0; // at target rate
 
@@ -667,7 +694,7 @@
 
     bool looping = m_viewManager->getPlayLoopMode();
 
-    RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, targetRate);
+    RealTime inbuffer_t = RealTime::frame2RealTime(inbuffer, rate);
 
     sv_frame_t stretchlat = 0;
     double timeRatio = 1.0;
@@ -677,7 +704,7 @@
         timeRatio = m_timeStretcher->getTimeRatio();
     }
 
-    RealTime stretchlat_t = RealTime::frame2RealTime(stretchlat, targetRate);
+    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
@@ -695,8 +722,7 @@
         m_trustworthyTimestamps &&
         lastRetrievalTimestamp != 0.0) {
 
-        lastretrieved_t = RealTime::frame2RealTime
-            (lastRetrievedBlockSize, targetRate);
+        lastretrieved_t = RealTime::frame2RealTime(lastRetrievedBlockSize, rate);
 
         // calculate number of frames at target rate that have elapsed
         // since the end of the last call to getSourceSamples
@@ -713,11 +739,10 @@
 
     } else {
 
-        lastretrieved_t = RealTime::frame2RealTime
-            (getTargetBlockSize(), targetRate);
+        lastretrieved_t = RealTime::frame2RealTime(getTargetBlockSize(), rate);
     }
 
-    RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, sourceRate);
+    RealTime bufferedto_t = RealTime::frame2RealTime(readBufferFill, rate);
 
     if (timeRatio != 1.0) {
         lastretrieved_t = lastretrieved_t / timeRatio;
@@ -743,7 +768,7 @@
             - latency_t - stretchlat_t - lastretrieved_t - inbuffer_t
             + sincerequest_t;
         if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime;
-        sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate);
+        sv_frame_t frame = RealTime::realTime2Frame(playing_t, rate);
         return m_viewManager->alignPlaybackFrameToReference(frame);
     }
 
@@ -780,8 +805,7 @@
     // duration of playback!
 
     if (!m_playStartFramePassed) {
-        RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame,
-                                                        sourceRate);
+        RealTime playstart_t = RealTime::frame2RealTime(m_playStartFrame, rate);
         if (playing_t < playstart_t) {
 //            cerr << "playing_t " << playing_t << " < playstart_t " 
 //                      << playstart_t << endl;
@@ -839,7 +863,7 @@
 
     if (playing_t < RealTime::zeroTime) playing_t = RealTime::zeroTime;
 
-    sv_frame_t frame = RealTime::realTime2Frame(playing_t, sourceRate);
+    sv_frame_t frame = RealTime::realTime2Frame(playing_t, rate);
 
     if (m_lastCurrentFrame > 0 && !looping) {
         if (frame < m_lastCurrentFrame) {
@@ -924,19 +948,11 @@
 void
 AudioCallbackPlaySource::setSystemPlaybackSampleRate(int sr)
 {
-    bool first = (m_targetSampleRate == 0);
-
-    m_targetSampleRate = sr;
-
-    if (first && (m_stretchRatio != 1.f)) {
-        // couldn't create a stretcher before because we had no sample
-        // rate: make one now
-        setTimeStretch(m_stretchRatio);
-    }
+    m_deviceSampleRate = sr;
 }
 
 void
-AudioCallbackPlaySource::setSystemPlaybackChannelCount(int c)
+AudioCallbackPlaySource::setSystemPlaybackChannelCount(int)
 {
 }
 
@@ -969,10 +985,9 @@
 }
 
 sv_samplerate_t
-AudioCallbackPlaySource::getTargetSampleRate() const
+AudioCallbackPlaySource::getDeviceSampleRate() const
 {
-    if (m_targetSampleRate) return m_targetSampleRate;
-    else return getSourceSampleRate();
+    return m_deviceSampleRate;
 }
 
 int
@@ -999,19 +1014,20 @@
 {
     m_stretchRatio = factor;
 
-    if (!getTargetSampleRate()) return; // have to make our stretcher later
+    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
-            (int(getTargetSampleRate()),
+            (rate,
              m_stretcherInputCount,
              RubberBandStretcher::OptionProcessRealTime,
              factor);
         RubberBandStretcher *monoStretcher = new RubberBandStretcher
-            (int(getTargetSampleRate()),
+            (rate,
              1,
              RubberBandStretcher::OptionProcessRealTime,
              factor);
@@ -1308,6 +1324,9 @@
         return false;
     }
 
+    // space is now the number of samples that can be written on each
+    // channel's write ringbuffer
+    
     sv_frame_t f = m_writeBufferFill;
 	
     bool readWriteEqual = (m_readBuffers == m_writeBuffers);
@@ -1325,8 +1344,6 @@
 
     int channels = getTargetChannelCount();
 
-    sv_frame_t orig = space;
-
     static float **bufferPtrs = 0;
     static int bufferPtrCount = 0;
 
--- a/audio/AudioCallbackPlaySource.h	Tue Dec 06 15:59:06 2016 +0000
+++ b/audio/AudioCallbackPlaySource.h	Fri Dec 09 18:01:55 2016 +0000
@@ -39,6 +39,10 @@
     class RubberBandStretcher;
 }
 
+namespace breakfastquay {
+    class ResamplerWrapper;
+}
+
 class Model;
 class ViewManagerBase;
 class AudioGenerator;
@@ -121,6 +125,11 @@
     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
      * called by the target class.
      */
@@ -136,7 +145,7 @@
 
     /**
      * Set the playback latency of the target audio device, in frames
-     * at the target sample rate.  This is the difference between the
+     * at the device sample rate.  This is the difference between the
      * frame currently "leaving the speakers" and the last frame (or
      * highest last frame across all channels) requested via
      * getSamples().  The default is zero.
@@ -161,7 +170,7 @@
      * Return the sample rate set by the target audio device (or the
      * source sample rate if the target hasn't set one).
      */
-    virtual sv_samplerate_t getTargetSampleRate() const;
+    sv_samplerate_t getDeviceSampleRate() const;
 
     /**
      * Indicate how many channels the target audio device was opened
@@ -194,30 +203,45 @@
      * to the play target.  This may be more than the source channel
      * count: for example, a mono source will provide 2 channels
      * after pan.
+     *
      * This may safely be called from a realtime thread.  Returns 0 if
      * there is no source yet available.
+     *
+     * override from AudioPlaySource
      */
-    int getTargetChannelCount() const;
+    virtual int getTargetChannelCount() const override;
 
     /**
      * ApplicationPlaybackSource equivalent of the above.
+     *
+     * override from breakfastquay::ApplicationPlaybackSource
      */
-    virtual int getApplicationChannelCount() const {
+    virtual int getApplicationChannelCount() const override {
         return getTargetChannelCount();
     }
     
     /**
-     * Get the actual sample rate of the source material.  This may
-     * safely be called from a realtime thread.  Returns 0 if there is
-     * no source yet available.
+     * Get the actual sample rate of the source material (the main
+     * model).  This may safely be called from a realtime thread.
+     * Returns 0 if there is no source yet available.
+     *
+     * When this changes, the AudioCallbackPlaySource notifies its
+     * ResamplerWrapper of the new sample rate so that it can resample
+     * correctly on the way to the device (which is opened at a fixed
+     * rate, see getApplicationSampleRate).
      */
-    virtual sv_samplerate_t getSourceSampleRate() const;
+    virtual sv_samplerate_t getSourceSampleRate() const override;
 
     /**
-     * ApplicationPlaybackSource equivalent of the above.
+     * ApplicationPlaybackSource interface method: get the sample rate
+     * at which the application wants the device to be opened. We
+     * always allow the device to open at its default rate, and then
+     * we resample if the audio is at a different rate. This avoids
+     * having to close and re-open the device to obtain consistent
+     * behaviour for consecutive sessions with different source rates.
      */
-    virtual int getApplicationSampleRate() const {
-        return int(round(getSourceSampleRate()));
+    virtual int getApplicationSampleRate() const override {
+        return 0;
     }
 
     /**
@@ -311,7 +335,7 @@
     int                               m_sourceChannelCount;
     sv_frame_t                        m_blockSize;
     sv_samplerate_t                   m_sourceSampleRate;
-    sv_samplerate_t                   m_targetSampleRate;
+    sv_samplerate_t                   m_deviceSampleRate;
     sv_frame_t                        m_playLatency;
     breakfastquay::SystemPlaybackTarget *m_target;
     double                            m_lastRetrievalTimestamp;
@@ -396,6 +420,7 @@
     QMutex m_mutex;
     QWaitCondition m_condition;
     FillThread *m_fillThread;
+    breakfastquay::ResamplerWrapper *m_resamplerWrapper; // I don't own this
 };
 
 #endif
--- a/framework/MainWindowBase.cpp	Tue Dec 06 15:59:06 2016 +0000
+++ b/framework/MainWindowBase.cpp	Fri Dec 09 18:01:55 2016 +0000
@@ -76,6 +76,7 @@
 #include <bqaudioio/SystemPlaybackTarget.h>
 #include <bqaudioio/SystemAudioIO.h>
 #include <bqaudioio/AudioFactory.h>
+#include <bqaudioio/ResamplerWrapper.h>
 
 #include <QApplication>
 #include <QMessageBox>
@@ -141,6 +142,7 @@
     m_soundOptions(options),
     m_playSource(0),
     m_recordTarget(0),
+    m_resamplerWrapper(0),
     m_playTarget(0),
     m_audioIO(0),
     m_oscQueue(0),
@@ -303,6 +305,7 @@
     delete m_playTarget;
 
     // Then delete the Application objects.
+    delete m_resamplerWrapper;
     delete m_playSource;
     delete m_recordTarget;
     
@@ -1399,7 +1402,9 @@
     if (Preferences::getInstance()->getFixedSampleRate() != 0) {
         rate = Preferences::getInstance()->getFixedSampleRate();
     } else if (Preferences::getInstance()->getResampleOnLoad()) {
-        rate = m_playSource->getSourceSampleRate();
+        if (getMainModel()) {
+            rate = getMainModel()->getSampleRate();
+        }
     }
 
     ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(source, rate);
@@ -2158,7 +2163,9 @@
     if (getMainModel()) {
         rate = getMainModel()->getSampleRate();
     } else if (Preferences::getInstance()->getResampleOnLoad()) {
-        rate = m_playSource->getSourceSampleRate();
+        if (getMainModel()) {
+            rate = getMainModel()->getSampleRate();
+        }
     }
 
     RDFImporter importer
@@ -2300,27 +2307,49 @@
 
     if (!(m_soundOptions & WithAudioOutput)) return;
 
-    //!!! how to handle preferences
-/*    
     QSettings settings;
     settings.beginGroup("Preferences");
-    QString targetName = settings.value("audio-target", "").toString();
+    QString implementation = settings.value
+        ("audio-target", "").toString();
+    QString suffix;
+    if (implementation != "") suffix = "-" + implementation;
+    QString recordDevice = settings.value
+        ("audio-record-device" + suffix, "").toString();
+    QString playbackDevice = settings.value
+        ("audio-playback-device" + suffix, "").toString();
     settings.endGroup();
-    AudioTargetFactory *factory = AudioTargetFactory::getInstance();
-
-    factory->setDefaultCallbackTarget(targetName);
-*/
-
+
+    if (implementation == "auto") {
+        implementation = "";
+    }
+    
+    breakfastquay::AudioFactory::Preference preference;
+    preference.implementation = implementation.toStdString();
+    preference.recordDevice = recordDevice.toStdString();
+    preference.playbackDevice = playbackDevice.toStdString();
+
+    SVCERR << "createAudioIO: Preferred implementation = \""
+            << preference.implementation << "\"" << endl;
+    SVCERR << "createAudioIO: Preferred playback device = \""
+            << preference.playbackDevice << "\"" << endl;
+    SVCERR << "createAudioIO: Preferred record device = \""
+            << preference.recordDevice << "\"" << endl;
+
+    if (!m_resamplerWrapper) {
+        m_resamplerWrapper = new breakfastquay::ResamplerWrapper(m_playSource);
+        m_playSource->setResamplerWrapper(m_resamplerWrapper);
+    }
+    
     if (m_soundOptions & WithAudioInput) {
         m_audioIO = breakfastquay::AudioFactory::
-            createCallbackIO(m_recordTarget, m_playSource);
+            createCallbackIO(m_recordTarget, m_resamplerWrapper, preference);
         if (m_audioIO) {
             m_audioIO->suspend(); // start in suspended state
             m_playSource->setSystemPlaybackTarget(m_audioIO);
         }
     } else {
         m_playTarget = breakfastquay::AudioFactory::
-            createCallbackPlayTarget(m_playSource);
+            createCallbackPlayTarget(m_resamplerWrapper, preference);
         if (m_playTarget) {
             m_playTarget->suspend(); // start in suspended state
             m_playSource->setSystemPlaybackTarget(m_playTarget);
@@ -2329,22 +2358,20 @@
 
     if (!m_playTarget && !m_audioIO) {
         emit hideSplash();
-
-//        if (factory->isAutoCallbackTarget(targetName)) {
+        if (implementation == "") {
             QMessageBox::warning
 	    (this, tr("Couldn't open audio device"),
 	     tr("<b>No audio available</b><p>Could not open an audio device for playback.<p>Automatic audio device detection failed. Audio playback will not be available during this session.</p>"),
 	     QMessageBox::Ok);
-/*
         } else {
             QMessageBox::warning
                 (this, tr("Couldn't open audio device"),
                  tr("<b>No audio available</b><p>Failed to open your preferred audio device (\"%1\").<p>Audio playback will not be available during this session.</p>")
-                 .arg(factory->getCallbackTargetDescription(targetName)),
+                 .arg(breakfastquay::AudioFactory::
+                      getImplementationDescription(implementation.toStdString())
+                      .c_str()),
                  QMessageBox::Ok);
         }
-*/
-            return;
     }
 }
 
--- a/framework/MainWindowBase.h	Tue Dec 06 15:59:06 2016 +0000
+++ b/framework/MainWindowBase.h	Fri Dec 09 18:01:55 2016 +0000
@@ -65,8 +65,9 @@
 class AlignmentModel;
 
 namespace breakfastquay {
-class SystemPlaybackTarget;
-class SystemAudioIO;
+    class SystemPlaybackTarget;
+    class SystemAudioIO;
+    class ResamplerWrapper;
 }
 
 /**
@@ -343,6 +344,7 @@
 
     AudioCallbackPlaySource *m_playSource;
     AudioRecordTarget       *m_recordTarget;
+    breakfastquay::ResamplerWrapper *m_resamplerWrapper;
     breakfastquay::SystemPlaybackTarget *m_playTarget; // only one of this...
     breakfastquay::SystemAudioIO *m_audioIO;           // ... and this exists