# HG changeset patch # User Chris Cannam # Date 1481306515 0 # Node ID 4be200469a9c0a23987970f49e452bd61a216828 # Parent 4de547a5905ce355e046277713ca0f74c59839fc# Parent 2a1e9e017484ec0bf336bc22effc24c47b0e7ade Merge from branch bqresample diff -r 4de547a5905c -r 4be200469a9c audio/AudioCallbackPlaySource.cpp --- 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 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; diff -r 4de547a5905c -r 4be200469a9c audio/AudioCallbackPlaySource.h --- 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 diff -r 4de547a5905c -r 4be200469a9c framework/MainWindowBase.cpp --- 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 #include #include +#include #include #include @@ -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("No audio available

Could not open an audio device for playback.

Automatic audio device detection failed. Audio playback will not be available during this session.

"), QMessageBox::Ok); -/* } else { QMessageBox::warning (this, tr("Couldn't open audio device"), tr("No audio available

Failed to open your preferred audio device (\"%1\").

Audio playback will not be available during this session.

") - .arg(factory->getCallbackTargetDescription(targetName)), + .arg(breakfastquay::AudioFactory:: + getImplementationDescription(implementation.toStdString()) + .c_str()), QMessageBox::Ok); } -*/ - return; } } diff -r 4de547a5905c -r 4be200469a9c framework/MainWindowBase.h --- 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