# HG changeset patch # User Chris Cannam # Date 1481725721 0 # Node ID 07e111dd590227a6fc27ead995859a7770acc2e3 # Parent fcac6c6b8deb3c78c4c453b4271a198775bd71d7# Parent ce3818cd16c578fb40dd2d30f2c94090026582aa Merge from branch 3.0-integration diff -r fcac6c6b8deb -r 07e111dd5902 audio/AudioCallbackPlaySource.cpp --- a/audio/AudioCallbackPlaySource.cpp Mon Dec 05 17:03:09 2016 +0000 +++ b/audio/AudioCallbackPlaySource.cpp Wed Dec 14 14:28:41 2016 +0000 @@ -28,10 +28,15 @@ #include "plugin/RealTimePluginInstance.h" #include "bqaudioio/SystemPlaybackTarget.h" +#include "bqaudioio/ResamplerWrapper.h" + +#include "bqvec/VectorOps.h" #include using namespace RubberBand; +using breakfastquay::v_zero_channels; + #include #include @@ -53,7 +58,8 @@ m_sourceChannelCount(0), m_blockSize(1024), m_sourceSampleRate(0), - m_targetSampleRate(0), + m_deviceSampleRate(0), + m_deviceChannelCount(0), m_playLatency(0), m_target(0), m_lastRetrievalTimestamp(0.0), @@ -78,7 +84,7 @@ m_stretcherInputs(0), m_stretcherInputSizes(0), m_fillThread(0), - m_converter(0) + m_resamplerWrapper(0) { m_viewManager->setAudioPlaySource(this); @@ -157,7 +163,7 @@ m_lastModelEndFrame = model->getEndFrame(); } - bool buffersChanged = false, srChanged = false; + bool buffersIncreased = false, srChanged = false; int modelChannels = 1; ReadOnlyWaveFileModel *rowfm = qobject_cast(model); @@ -224,49 +230,60 @@ if (!m_writeBuffers || (int)m_writeBuffers->size() < getTargetChannelCount()) { clearRingBuffers(true, getTargetChannelCount()); - buffersChanged = true; + buffersIncreased = true; } else { if (willPlay) clearRingBuffers(true); } - if (buffersChanged || srChanged) { - if (m_converter) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "AudioCallbackPlaySource::addModel: Buffers or sample rate changed, deleting existing SR converter" << endl; -#endif - src_delete(m_converter); - m_converter = 0; - } + if (srChanged) { + + 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(); m_mutex.unlock(); - initialiseConverter(); - 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) -- emitting modelReplaced" << endl; + SVDEBUG << "AudioCallbackPlaySource::addModel: now have " << m_models.size() << " model(s)" << endl; #endif - if (buffersChanged || srChanged) { - emit modelReplaced(); - } - connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), this, SLOT(modelChangedWithin(sv_frame_t, sv_frame_t))); #ifdef DEBUG_AUDIO_PLAY_SOURCE cout << "AudioCallbackPlaySource::addModel: awakening thread" << endl; #endif - + m_condition.wakeAll(); } @@ -301,13 +318,6 @@ m_models.erase(model); if (m_models.empty()) { - if (m_converter) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "AudioCallbackPlaySource::removeModel: No models left, deleting SR converter" << endl; -#endif - src_delete(m_converter); - m_converter = 0; - } m_sourceSampleRate = 0; } @@ -344,14 +354,6 @@ m_models.clear(); - if (m_converter) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "AudioCallbackPlaySource::clearModels: Deleting SR converter" << endl; -#endif - src_delete(m_converter); - m_converter = 0; - } - m_lastModelEndFrame = 0; m_sourceSampleRate = 0; @@ -369,7 +371,7 @@ if (!haveLock) m_mutex.lock(); #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "clearRingBuffers" << endl; + cout << "clearRingBuffers" << endl; #endif rebuildRangeLists(); @@ -379,15 +381,15 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "current playing frame = " << getCurrentPlayingFrame() << endl; + cout << "current playing frame = " << getCurrentPlayingFrame() << endl; - cerr << "write buffer fill (before) = " << m_writeBufferFill << endl; + cout << "write buffer fill (before) = " << m_writeBufferFill << endl; #endif m_writeBufferFill = getCurrentBufferedFrame(); #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "current buffered frame = " << m_writeBufferFill << endl; + cout << "current buffered frame = " << m_writeBufferFill << endl; #endif if (m_readBuffers != m_writeBuffers) { @@ -416,18 +418,22 @@ if (!m_target) return; if (!m_sourceSampleRate) { - cerr << "AudioCallbackPlaySource::play: No source sample rate available, not playing" << endl; + SVCERR << "AudioCallbackPlaySource::play: No source sample rate available, not playing" << endl; return; } if (m_viewManager->getPlaySelectionMode() && !m_viewManager->getSelections().empty()) { - SVDEBUG << "AudioCallbackPlaySource::play: constraining frame " << startFrame << " to selection = "; +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "AudioCallbackPlaySource::play: constraining frame " << startFrame << " to selection = "; +#endif startFrame = m_viewManager->constrainFrameToSelection(startFrame); - SVDEBUG << startFrame << endl; +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << startFrame << endl; +#endif } else { if (startFrame < 0) { @@ -439,13 +445,13 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "play(" << startFrame << ") -> playback model "; + cout << "play(" << startFrame << ") -> aligned playback model "; #endif startFrame = m_viewManager->alignReferenceToPlaybackFrame(startFrame); #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << startFrame << endl; + cout << startFrame << endl; #endif // The fill thread will automatically empty its buffers before @@ -467,12 +473,11 @@ for (int c = 0; c < getTargetChannelCount(); ++c) { RingBuffer *rb = getReadRingBuffer(c); #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "reset ring buffer for channel " << c << endl; + cout << "reset ring buffer for channel " << c << endl; #endif if (rb) rb->reset(); } } - if (m_converter) src_reset(m_converter); m_mutex.unlock(); @@ -556,14 +561,14 @@ } void -AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName n) +AudioCallbackPlaySource::preferenceChanged(PropertyContainer::PropertyName ) { } void AudioCallbackPlaySource::audioProcessingOverload() { - cerr << "Audio processing overload!" << endl; + SVCERR << "Audio processing overload!" << endl; if (!m_playing) return; @@ -587,10 +592,25 @@ void AudioCallbackPlaySource::setSystemPlaybackTarget(breakfastquay::SystemPlaybackTarget *target) { + if (target == 0) { + // reset target-related facts and figures + m_deviceSampleRate = 0; + m_deviceChannelCount = 0; + } m_target = target; } 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; @@ -599,7 +619,7 @@ } if (size * 4 > m_ringBufferSize) { #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "AudioCallbackPlaySource::setTarget: Buffer size " + cout << "AudioCallbackPlaySource::setTarget: Buffer size " << size << " > a quarter of ring buffer size " << m_ringBufferSize << ", calling for more ring buffer" << endl; @@ -636,12 +656,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); @@ -656,16 +676,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 @@ -685,7 +707,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; @@ -695,7 +717,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 @@ -713,8 +735,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 @@ -731,11 +752,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; @@ -744,7 +764,7 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved quantity: " << lastretrieved_t << endl; + cout << "\nbuffered to: " << bufferedto_t << ", in buffer: " << inbuffer_t << ", time ratio " << timeRatio << "\n stretcher latency: " << stretchlat_t << ", device latency: " << latency_t << "\n since request: " << sincerequest_t << ", last retrieved quantity: " << lastretrieved_t << endl; #endif // Normally the range lists should contain at least one item each @@ -761,7 +781,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); } @@ -798,15 +818,14 @@ // 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 " +// cout << "playing_t " << playing_t << " < playstart_t " // << playstart_t << endl; if (/*!!! sincerequest_t > RealTime::zeroTime && */ m_playStartedAt + latency_t + stretchlat_t < RealTime::fromSeconds(currentTime)) { -// cerr << "but we've been playing for long enough that I think we should disregard it (it probably results from loop wrapping)" << endl; +// cout << "but we've been playing for long enough that I think we should disregard it (it probably results from loop wrapping)" << endl; m_playStartFramePassed = true; } else { playing_t = playstart_t; @@ -817,13 +836,13 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "playing_t " << playing_t; + cout << "playing_t " << playing_t; #endif playing_t = playing_t - m_rangeStarts[inRange]; #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << " as offset into range " << inRange << " (start =" << m_rangeStarts[inRange] << " duration =" << m_rangeDurations[inRange] << ") = " << playing_t << endl; + cout << " as offset into range " << inRange << " (start =" << m_rangeStarts[inRange] << " duration =" << m_rangeDurations[inRange] << ") = " << playing_t << endl; #endif while (playing_t < RealTime::zeroTime) { @@ -844,20 +863,20 @@ playing_t = playing_t + m_rangeStarts[inRange]; #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << " playing time: " << playing_t << endl; + cout << " playing time: " << playing_t << endl; #endif if (!looping) { if (inRange == (int)m_rangeStarts.size()-1 && playing_t >= m_rangeStarts[inRange] + m_rangeDurations[inRange]) { -cerr << "Not looping, inRange " << inRange << " == rangeStarts.size()-1, playing_t " << playing_t << " >= m_rangeStarts[inRange] " << m_rangeStarts[inRange] << " + m_rangeDurations[inRange] " << m_rangeDurations[inRange] << " -- stopping" << endl; +cout << "Not looping, inRange " << inRange << " == rangeStarts.size()-1, playing_t " << playing_t << " >= m_rangeStarts[inRange] " << m_rangeStarts[inRange] << " + m_rangeDurations[inRange] " << m_rangeDurations[inRange] << " -- stopping" << endl; stop(); } } 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) { @@ -920,7 +939,7 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "Now have " << m_rangeStarts.size() << " play ranges" << endl; + cout << "Now have " << m_rangeStarts.size() << " play ranges" << endl; #endif } @@ -942,59 +961,13 @@ void AudioCallbackPlaySource::setSystemPlaybackSampleRate(int sr) { - bool first = (m_targetSampleRate == 0); - - m_targetSampleRate = sr; - initialiseConverter(); - - 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::initialiseConverter() +AudioCallbackPlaySource::setSystemPlaybackChannelCount(int count) { - m_mutex.lock(); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cerr << "AudioCallbackPlaySource::initialiseConverter(): from " - << getSourceSampleRate() << " to " << getTargetSampleRate() << endl; -#endif - - if (m_converter) { - src_delete(m_converter); - m_converter = 0; - } - - if (getSourceSampleRate() != getTargetSampleRate()) { - - int err = 0; - - m_converter = src_new(SRC_SINC_FASTEST, getTargetChannelCount(), &err); - - if (!m_converter) { - cerr << "AudioCallbackPlaySource::setModel: ERROR in creating samplerate converter: " - << src_strerror(err) << endl; - - m_mutex.unlock(); - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - false); - } else { - - m_mutex.unlock(); - - emit sampleRateMismatch(getSourceSampleRate(), - getTargetSampleRate(), - true); - } - } else { - m_mutex.unlock(); - } + m_deviceChannelCount = count; } void @@ -1002,7 +975,7 @@ { RealTimePluginInstance *plugin = dynamic_cast(a); if (a && !plugin) { - cerr << "WARNING: AudioCallbackPlaySource::setAuditioningEffect: auditionable object " << a << " is not a real-time plugin instance" << endl; + SVCERR << "WARNING: AudioCallbackPlaySource::setAuditioningEffect: auditionable object " << a << " is not a real-time plugin instance" << endl; } m_mutex.lock(); @@ -1026,10 +999,9 @@ } sv_samplerate_t -AudioCallbackPlaySource::getTargetSampleRate() const +AudioCallbackPlaySource::getDeviceSampleRate() const { - if (m_targetSampleRate) return m_targetSampleRate; - else return getSourceSampleRate(); + return m_deviceSampleRate; } int @@ -1045,6 +1017,12 @@ return m_sourceChannelCount; } +int +AudioCallbackPlaySource::getDeviceChannelCount() const +{ + return m_deviceChannelCount; +} + sv_samplerate_t AudioCallbackPlaySource::getSourceSampleRate() const { @@ -1056,19 +1034,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); @@ -1086,33 +1065,84 @@ } 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; + cout << "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; + cout << "AudioCallbackPlaySource::getSourceSamples: Playing" << endl; #endif // 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 *rb = getReadRingBuffer(ch); if (!rb) { - cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: " + SVCERR << "WARNING: AudioCallbackPlaySource::getSourceSamples: " << "No ring buffer available for channel " << ch << ", returning no data here" << endl; count = 0; @@ -1142,7 +1172,7 @@ if (ratio != m_stretchRatio) { if (!ts) { - cerr << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << endl; + SVCERR << "WARNING: AudioCallbackPlaySource::getSourceSamples: Time ratio change to " << m_stretchRatio << " is pending, but no stretcher is set" << endl; m_stretchRatio = 1.0; } else { ts->setTimeRatio(m_stretchRatio); @@ -1170,7 +1200,11 @@ int got = 0; - for (int ch = 0; ch < getTargetChannelCount(); ++ch) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cout << "channels == " << channels << endl; +#endif + + for (int ch = 0; ch < channels; ++ch) { RingBuffer *rb = getReadRingBuffer(ch); @@ -1188,7 +1222,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; } @@ -1206,7 +1240,6 @@ return got; } - int channels = getTargetChannelCount(); sv_frame_t available; sv_frame_t fedToStretcher = 0; int warned = 0; @@ -1223,14 +1256,14 @@ sv_frame_t got = reqd; #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "reqd = " <getReadSpace() << " remain" << endl; } #endif } else { - cerr << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << endl; + SVCERR << "WARNING: No ring buffer available for channel " << c << " in stretcher input block" << endl; } } if (got < reqd) { - cerr << "WARNING: Read underrun in playback (" + SVCERR << "WARNING: Read underrun in playback (" << got << " < " << reqd << ")" << endl; } @@ -1274,18 +1307,14 @@ if (got == 0) break; if (ts->available() == available) { - cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << endl; + SVCERR << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples (warned = " << warned << ")" << endl; if (++warned == 5) break; } } 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); @@ -1299,26 +1328,26 @@ } 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; if (!plugin) return; if ((int)plugin->getAudioInputCount() != getTargetChannelCount()) { -// cerr << "plugin input count " << plugin->getAudioInputCount() +// cout << "plugin input count " << plugin->getAudioInputCount() // << " != our channel count " << getTargetChannelCount() // << endl; return; } if ((int)plugin->getAudioOutputCount() != getTargetChannelCount()) { -// cerr << "plugin output count " << plugin->getAudioOutputCount() +// cout << "plugin output count " << plugin->getAudioOutputCount() // << " != our channel count " << getTargetChannelCount() // << endl; return; } if ((int)plugin->getBufferSize() < count) { -// cerr << "plugin buffer size " << plugin->getBufferSize() +// cout << "plugin buffer size " << plugin->getBufferSize() // << " < our block size " << count // << endl; return; @@ -1365,6 +1394,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); @@ -1380,17 +1412,8 @@ cout << "buffered to " << f << " already" << endl; #endif - bool resample = (getSourceSampleRate() != getTargetSampleRate()); - -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << (resample ? "" : "not ") << "resampling (source " << getSourceSampleRate() << ", target " << getTargetSampleRate() << ")" << endl; -#endif - int channels = getTargetChannelCount(); - sv_frame_t orig = space; - sv_frame_t got = 0; - static float **bufferPtrs = 0; static int bufferPtrCount = 0; @@ -1402,157 +1425,62 @@ sv_frame_t generatorBlockSize = m_audioGenerator->getBlockSize(); - if (resample && !m_converter) { - throw std::logic_error("Sample rates differ, but no converter available!"); + // space must be a multiple of generatorBlockSize + sv_frame_t reqSpace = space; + space = (reqSpace / generatorBlockSize) * generatorBlockSize; + if (space == 0) { +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "requested fill of " << reqSpace + << " is less than generator block size of " + << generatorBlockSize << ", leaving it" << endl; +#endif + return false; } - if (resample && m_converter) { + if (tmpSize < channels * space) { + delete[] tmp; + tmp = new float[channels * space]; + tmpSize = channels * space; + } - double ratio = - double(getTargetSampleRate()) / double(getSourceSampleRate()); - orig = sv_frame_t(double(orig) / ratio + 0.1); + for (int c = 0; c < channels; ++c) { - // orig must be a multiple of generatorBlockSize - orig = (orig / generatorBlockSize) * generatorBlockSize; - if (orig == 0) return false; + bufferPtrs[c] = tmp + c * space; + + for (int i = 0; i < space; ++i) { + tmp[c * space + i] = 0.0f; + } + } - sv_frame_t work = std::max(orig, space); + sv_frame_t got = mixModels(f, space, bufferPtrs); // also modifies f - // We only allocate one buffer, but we use it in two halves. - // We place the non-interleaved values in the second half of - // the buffer (orig samples for channel 0, orig samples for - // channel 1 etc), and then interleave them into the first - // half of the buffer. Then we resample back into the second - // half (interleaved) and de-interleave the results back to - // the start of the buffer for insertion into the ringbuffers. - // What a faff -- especially as we've already de-interleaved - // the audio data from the source file elsewhere before we - // even reach this point. - - if (tmpSize < channels * work * 2) { - delete[] tmp; - tmp = new float[channels * work * 2]; - tmpSize = channels * work * 2; - } + for (int c = 0; c < channels; ++c) { - float *nonintlv = tmp + channels * work; - float *intlv = tmp; - float *srcout = tmp + channels * work; - - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < orig; ++i) { - nonintlv[channels * i + c] = 0.0f; - } - } + RingBuffer *wb = getWriteRingBuffer(c); + if (wb) { + int actual = wb->write(bufferPtrs[c], int(got)); +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "Wrote " << actual << " samples for ch " << c << ", now " + << wb->getReadSpace() << " to read" + << endl; +#endif + if (actual < got) { + SVCERR << "WARNING: Buffer overrun in channel " << c + << ": wrote " << actual << " of " << got + << " samples" << endl; + } + } + } - for (int c = 0; c < channels; ++c) { - bufferPtrs[c] = nonintlv + c * orig; - } - - got = mixModels(f, orig, bufferPtrs); // also modifies f - - // and interleave into first half - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < got; ++i) { - float sample = nonintlv[c * got + i]; - intlv[channels * i + c] = sample; - } - } - - SRC_DATA data; - data.data_in = intlv; - data.data_out = srcout; - data.input_frames = long(got); - data.output_frames = long(work); - data.src_ratio = ratio; - data.end_of_input = 0; - - int err = src_process(m_converter, &data); - - sv_frame_t toCopy = sv_frame_t(double(got) * ratio + 0.1); - - if (err) { - cerr - << "AudioCallbackPlaySourceFillThread: ERROR in samplerate conversion: " - << src_strerror(err) << endl; - //!!! Then what? - } else { - got = data.input_frames_used; - toCopy = data.output_frames_gen; -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Resampled " << got << " frames to " << toCopy << " frames" << endl; -#endif - } - - for (int c = 0; c < channels; ++c) { - for (int i = 0; i < toCopy; ++i) { - tmp[i] = srcout[channels * i + c]; - } - RingBuffer *wb = getWriteRingBuffer(c); - if (wb) wb->write(tmp, int(toCopy)); - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; - - } else { - - // space must be a multiple of generatorBlockSize - sv_frame_t reqSpace = space; - space = (reqSpace / generatorBlockSize) * generatorBlockSize; - if (space == 0) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "requested fill of " << reqSpace - << " is less than generator block size of " - << generatorBlockSize << ", leaving it" << endl; -#endif - return false; - } - - if (tmpSize < channels * space) { - delete[] tmp; - tmp = new float[channels * space]; - tmpSize = channels * space; - } - - for (int c = 0; c < channels; ++c) { - - bufferPtrs[c] = tmp + c * space; - - for (int i = 0; i < space; ++i) { - tmp[c * space + i] = 0.0f; - } - } - - sv_frame_t got = mixModels(f, space, bufferPtrs); // also modifies f - - for (int c = 0; c < channels; ++c) { - - RingBuffer *wb = getWriteRingBuffer(c); - if (wb) { - int actual = wb->write(bufferPtrs[c], int(got)); -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Wrote " << actual << " samples for ch " << c << ", now " - << wb->getReadSpace() << " to read" - << endl; -#endif - if (actual < got) { - cerr << "WARNING: Buffer overrun in channel " << c - << ": wrote " << actual << " of " << got - << " samples" << endl; - } - } - } - - m_writeBufferFill = f; - if (readWriteEqual) m_readBufferFill = f; + m_writeBufferFill = f; + if (readWriteEqual) m_readBufferFill = f; #ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Read buffer fill is now " << m_readBufferFill << endl; + cout << "Read buffer fill is now " << m_readBufferFill << ", write buffer fill " + << m_writeBufferFill << endl; #endif - //!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples - } + //!!! how do we know when ended? need to mark up a fully-buffered flag and check this if we find the buffers empty in getSourceSamples return true; } @@ -1570,13 +1498,24 @@ bool constrained = (m_viewManager->getPlaySelectionMode() && !m_viewManager->getSelections().empty()); - static float **chunkBufferPtrs = 0; - static int chunkBufferPtrCount = 0; int channels = getTargetChannelCount(); #ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Selection playback: start " << frame << ", size " << count <<", channels " << channels << endl; + cout << "mixModels: start " << frame << ", size " << count << ", channels " << channels << endl; #endif +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + if (constrained) { + cout << "Manager has " << m_viewManager->getSelections().size() << " selection(s):" << endl; + for (auto sel: m_viewManager->getSelections()) { + cout << sel.getStartFrame() << " -> " << sel.getEndFrame() + << " (" << (sel.getEndFrame() - sel.getStartFrame()) << " frames)" + << endl; + } + } +#endif + + static float **chunkBufferPtrs = 0; + static int chunkBufferPtrCount = 0; if (chunkBufferPtrCount < channels) { if (chunkBufferPtrs) delete[] chunkBufferPtrs; @@ -1652,22 +1591,28 @@ } nextChunkStart = chunkStart + chunkSize; } - -// cout << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << endl; +#ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING + cout << "chunkStart " << chunkStart << ", chunkSize " << chunkSize << ", nextChunkStart " << nextChunkStart << ", frame " << frame << ", count " << count << ", processed " << processed << endl; +#endif + if (!chunkSize) { -#ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Ending selection playback at " << nextChunkStart << endl; -#endif // We need to maintain full buffers so that the other // thread can tell where it's got to in the playback -- so // return the full amount here frame = frame + count; + if (frame < nextChunkStart) { + frame = nextChunkStart; + } +#ifdef DEBUG_AUDIO_PLAY_SOURCE + cout << "mixModels: ending at " << nextChunkStart << ", returning frame as " + << frame << endl; +#endif return count; } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Selection playback: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << endl; + cout << "mixModels: chunk at " << chunkStart << " -> " << nextChunkStart << " (size " << chunkSize << ")" << endl; #endif if (selectionSize < 100) { @@ -1707,7 +1652,7 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE - cout << "Returning selection playback " << processed << " frames to " << nextChunkStart << endl; + cout << "mixModels returning " << processed << " frames to " << nextChunkStart << endl; #endif frame = nextChunkStart; @@ -1729,7 +1674,7 @@ // OK, we don't have enough and there's more to // read -- don't unify until we can do better #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: Not unifying: write buffer has less (" << wb->getReadSpace() << ") than " << m_blockSize*2 << " to read and write buffer fill (" << m_writeBufferFill << ") is not close to end frame (" << m_lastModelEndFrame << ")" << endl; + cout << "AudioCallbackPlaySource::unifyRingBuffers: Not unifying: write buffer has less (" << wb->getReadSpace() << ") than " << m_blockSize*2 << " to read and write buffer fill (" << m_writeBufferFill << ") is not close to end frame (" << m_lastModelEndFrame << ")" << endl; #endif return; } @@ -1749,7 +1694,7 @@ } #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - SVDEBUG << "AudioCallbackPlaySource::unifyRingBuffers: m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << endl; + cout << "AudioCallbackPlaySource::unifyRingBuffers: m_readBufferFill = " << m_readBufferFill << ", rf = " << rf << ", m_writeBufferFill = " << m_writeBufferFill << endl; #endif sv_frame_t wf = m_writeBufferFill; @@ -1779,7 +1724,7 @@ m_readBuffers = m_writeBuffers; m_readBufferFill = m_writeBufferFill; #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING - cerr << "unified" << endl; + cout << "unified" << endl; #endif } diff -r fcac6c6b8deb -r 07e111dd5902 audio/AudioCallbackPlaySource.h --- a/audio/AudioCallbackPlaySource.h Mon Dec 05 17:03:09 2016 +0000 +++ b/audio/AudioCallbackPlaySource.h Wed Dec 14 14:28:41 2016 +0000 @@ -39,6 +39,10 @@ class RubberBandStretcher; } +namespace breakfastquay { + class ResamplerWrapper; +} + class Model; class ViewManagerBase; class AudioGenerator; @@ -86,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 @@ -121,10 +125,15 @@ 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. */ - virtual void setSystemPlaybackBlockSize(int blockSize); + virtual void setSystemPlaybackBlockSize(int blockSize) override; /** * Get the block size of the target audio device. This may be an @@ -132,16 +141,16 @@ * size; the source should behave itself even if this value turns * out to be inaccurate. */ - int getTargetBlockSize() const; + virtual int getTargetBlockSize() const override; /** * 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. */ - void setSystemPlaybackLatency(int); + virtual void setSystemPlaybackLatency(int) override; /** * Get the playback latency of the target audio device. @@ -155,25 +164,34 @@ * 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 * source sample rate if the target hasn't set one). */ - virtual sv_samplerate_t getTargetSampleRate() const; + virtual sv_samplerate_t getDeviceSampleRate() const override; /** + * 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, so + * long as we provide the number of channels we specified when the + * target was started in getApplicationChannelCount(). + */ + virtual void setSystemPlaybackChannelCount(int) override; + + /** * Set the current output levels for metering (for call from the * target) */ - void setOutputLevels(float left, float right); + virtual void setOutputLevels(float left, float right) override; /** * Return the current (or thereabouts) output levels in the range * 0.0 -> 1.0, for metering purposes. */ - virtual bool getOutputLevels(float &left, float &right); + virtual bool getOutputLevels(float &left, float &right) override; /** * Get the number of channels of audio that in the source models. @@ -187,30 +205,52 @@ * 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; /** + * 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 */ - 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; } /** @@ -218,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). @@ -226,12 +266,6 @@ void setTimeStretch(double factor); /** - * Set the resampler quality, 0 - 2 where 0 is fastest and 2 is - * highest quality. - */ - void setResampleQuality(int q); - - /** * Set a single real-time plugin as a processing effect for * auditioning during playback. * @@ -246,7 +280,7 @@ * Pass a null pointer to remove the current auditioning plugin, * if any. */ - void setAuditioningEffect(Auditionable *plugin); + virtual void setAuditioningEffect(Auditionable *plugin) override; /** * Specify that only the given set of models should be played. @@ -259,24 +293,26 @@ */ void clearSoloModelSet(); - std::string getClientName() const { return m_clientName; } + virtual std::string getClientName() const override { + return m_clientName; + } signals: - void modelReplaced(); - void playStatusChanged(bool isPlaying); void sampleRateMismatch(sv_samplerate_t requested, sv_samplerate_t available, bool willResample); + void channelCountIncreased(); + void audioOverloadPluginDisabled(); void audioTimeStretchMultiChannelDisabled(); void activity(QString); public slots: - void audioProcessingOverload(); + void audioProcessingOverload() override; protected slots: void selectionChanged(); @@ -310,7 +346,8 @@ int m_sourceChannelCount; sv_frame_t m_blockSize; sv_samplerate_t m_sourceSampleRate; - sv_samplerate_t m_targetSampleRate; + sv_samplerate_t m_deviceSampleRate; + int m_deviceChannelCount; sv_frame_t m_playLatency; breakfastquay::SystemPlaybackTarget *m_target; double m_lastRetrievalTimestamp; @@ -370,7 +407,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 m_rangeStarts; @@ -395,9 +432,7 @@ QMutex m_mutex; QWaitCondition m_condition; FillThread *m_fillThread; - SRC_STATE *m_converter; - int m_resampleQuality; - void initialiseConverter(); + breakfastquay::ResamplerWrapper *m_resamplerWrapper; // I don't own this }; #endif diff -r fcac6c6b8deb -r 07e111dd5902 audio/AudioRecordTarget.cpp --- a/audio/AudioRecordTarget.cpp Mon Dec 05 17:03:09 2016 +0000 +++ b/audio/AudioRecordTarget.cpp Wed Dec 14 14:28:41 2016 +0000 @@ -27,6 +27,7 @@ m_clientName(clientName.toUtf8().data()), m_recording(false), m_recordSampleRate(44100), + m_recordChannelCount(2), m_frameCount(0), m_model(0) { @@ -37,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) { @@ -54,7 +67,13 @@ } void -AudioRecordTarget::putSamples(int nframes, float **samples) +AudioRecordTarget::setSystemRecordChannelCount(int c) +{ + m_recordChannelCount = c; +} + +void +AudioRecordTarget::putSamples(const float *const *samples, int, int nframes) { bool secChanged = false; sv_frame_t frameToEmit = 0; @@ -153,7 +172,9 @@ m_audioFileName = recordedDir.filePath(filename); - m_model = new WritableWaveFileModel(m_recordSampleRate, 2, m_audioFileName); + m_model = new WritableWaveFileModel(m_recordSampleRate, + m_recordChannelCount, + m_audioFileName); if (!m_model->isOK()) { cerr << "ERROR: AudioRecordTarget::startRecording: Recording failed" diff -r fcac6c6b8deb -r 07e111dd5902 audio/AudioRecordTarget.h --- a/audio/AudioRecordTarget.h Mon Dec 05 17:03:09 2016 +0000 +++ b/audio/AudioRecordTarget.h Wed Dec 14 14:28:41 2016 +0000 @@ -36,20 +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 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(); @@ -71,6 +72,7 @@ std::string m_clientName; bool m_recording; sv_samplerate_t m_recordSampleRate; + int m_recordChannelCount; sv_frame_t m_frameCount; QString m_audioFileName; WritableWaveFileModel *m_model; diff -r fcac6c6b8deb -r 07e111dd5902 framework/MainWindowBase.cpp --- a/framework/MainWindowBase.cpp Mon Dec 05 17:03:09 2016 +0000 +++ b/framework/MainWindowBase.cpp Wed Dec 14 14:28:41 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), @@ -240,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()), @@ -293,14 +297,8 @@ // depends on whether we handle recording or not) before we delete // the ApplicationPlaybackSource and ApplicationRecordTarget that // they refer to. - - // First prevent this trying to call target. - if (m_playSource) m_playSource->setSystemPlaybackTarget(0); - - // Then delete the breakfastquay::System object. - // Only one of these two exists! - delete m_audioIO; - delete m_playTarget; + + deleteAudioIO(); // Then delete the Application objects. delete m_playSource; @@ -1399,7 +1397,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 +2158,9 @@ if (getMainModel()) { rate = getMainModel()->getSampleRate(); } else if (Preferences::getInstance()->getResampleOnLoad()) { - rate = m_playSource->getSourceSampleRate(); + if (getMainModel()) { + rate = getMainModel()->getSampleRate(); + } } RDFImporter importer @@ -2300,27 +2302,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,25 +2353,54 @@ 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; } } +void +MainWindowBase::deleteAudioIO() +{ + // First prevent this trying to call target. + 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 +MainWindowBase::recreateAudioIO() +{ + deleteAudioIO(); + createAudioIO(); +} + WaveFileModel * MainWindowBase::getMainModel() { @@ -2826,6 +2879,8 @@ } } + if (m_viewManager) m_viewManager->setGlobalCentreFrame(0); + m_audioIO->resume(); WritableWaveFileModel *model = m_recordTarget->startRecording(); diff -r fcac6c6b8deb -r 07e111dd5902 framework/MainWindowBase.h --- a/framework/MainWindowBase.h Mon Dec 05 17:03:09 2016 +0000 +++ b/framework/MainWindowBase.h Wed Dec 14 14:28:41 2016 +0000 @@ -66,8 +66,9 @@ class AlignmentModel; namespace breakfastquay { -class SystemPlaybackTarget; -class SystemAudioIO; + class SystemPlaybackTarget; + class SystemAudioIO; + class ResamplerWrapper; } /** @@ -198,6 +199,7 @@ public slots: virtual void preferenceChanged(PropertyContainer::PropertyName); virtual void resizeConstrained(QSize); + virtual void recreateAudioIO(); protected slots: virtual void zoomIn(); @@ -344,6 +346,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 @@ -463,6 +466,8 @@ virtual void setDefaultSessionTemplate(QString); virtual void createAudioIO(); + virtual void deleteAudioIO(); + virtual void openHelpUrl(QString url); virtual void openLocalFolder(QString path);