Mercurial > hg > svapp
changeset 564:07e111dd5902 levelpanwidget
Merge from branch 3.0-integration
author | Chris Cannam |
---|---|
date | Wed, 14 Dec 2016 14:28:41 +0000 |
parents | fcac6c6b8deb (current diff) ce3818cd16c5 (diff) |
children | be83e81d9156 |
files | framework/MainWindowBase.h |
diffstat | 6 files changed, 465 insertions(+), 402 deletions(-) [+] |
line wrap: on
line diff
--- 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 <rubberband/RubberBandStretcher.h> using namespace RubberBand; +using breakfastquay::v_zero_channels; + #include <iostream> #include <cassert> @@ -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<ReadOnlyWaveFileModel *>(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<float> *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<RealTimePluginInstance *>(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<float> *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<float> *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 = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << endl; + cout << "reqd = " <<reqd << ", channels = " << channels << ", ic = " << m_stretcherInputCount << endl; #endif for (int c = 0; c < channels; ++c) { if (c >= m_stretcherInputCount) continue; if (reqd > m_stretcherInputSizes[c]) { if (c == 0) { - cerr << "WARNING: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << endl; + SVDEBUG << "NOTE: resizing stretcher input buffer from " << m_stretcherInputSizes[c] << " to " << (reqd * 2) << endl; } delete[] m_stretcherInputs[c]; m_stretcherInputSizes[c] = reqd * 2; @@ -1252,18 +1285,18 @@ #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING if (c == 0) { - SVDEBUG << "feeding stretcher: got " << gotHere + cout << "feeding stretcher: got " << gotHere << ", " << rb->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<float> *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<float> *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<float> *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 }
--- 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<RealTime> 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
--- 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"
--- 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;
--- 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 <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), @@ -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("<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; } } +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();
--- 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);