diff --git a/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp b/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp index 9790ad2dc5..6b2a198eb6 100644 --- a/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp +++ b/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp @@ -65,6 +65,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne buffer.setSize (numberOfChannels, bufferSizeNeeded); buffer.clear(); + const ScopedLock sl (bufferRangeLock); + bufferValidStart = 0; bufferValidEnd = 0; @@ -72,6 +74,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne do { + const ScopedUnlock ul (bufferRangeLock); + backgroundThread.moveToFrontOfQueue (this); Thread::sleep (5); } @@ -96,100 +100,97 @@ void BufferingAudioSource::releaseResources() void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { - const ScopedLock sl (bufferStartPosLock); + const auto bufferRange = getValidBufferRange (info.numSamples); - auto start = bufferValidStart.load(); - auto end = bufferValidEnd.load(); - auto pos = nextPlayPos.load(); - - auto validStart = (int) (jlimit (start, end, pos) - pos); - auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos); - - if (validStart == validEnd) + if (bufferRange.isEmpty()) { // total cache miss info.clearActiveBufferRegion(); + return; } - else + + const auto validStart = bufferRange.getStart(); + const auto validEnd = bufferRange.getEnd(); + + const ScopedLock sl (callbackLock); + + if (validStart > 0) + info.buffer->clear (info.startSample, validStart); // partial cache miss at start + + if (validEnd < info.numSamples) + info.buffer->clear (info.startSample + validEnd, + info.numSamples - validEnd); // partial cache miss at end + + if (validStart < validEnd) { - if (validStart > 0) - info.buffer->clear (info.startSample, validStart); // partial cache miss at start - - if (validEnd < info.numSamples) - info.buffer->clear (info.startSample + validEnd, - info.numSamples - validEnd); // partial cache miss at end - - if (validStart < validEnd) + for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) { - for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) + jassert (buffer.getNumSamples() > 0); + + const auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); + const auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); + + if (startBufferIndex < endBufferIndex) { - jassert (buffer.getNumSamples() > 0); - auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); - auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); + info.buffer->copyFrom (chan, info.startSample + validStart, + buffer, + chan, startBufferIndex, + validEnd - validStart); + } + else + { + const auto initialSize = buffer.getNumSamples() - startBufferIndex; - if (startBufferIndex < endBufferIndex) - { - info.buffer->copyFrom (chan, info.startSample + validStart, - buffer, - chan, startBufferIndex, - validEnd - validStart); - } - else - { - auto initialSize = buffer.getNumSamples() - startBufferIndex; + info.buffer->copyFrom (chan, info.startSample + validStart, + buffer, + chan, startBufferIndex, + initialSize); - info.buffer->copyFrom (chan, info.startSample + validStart, - buffer, - chan, startBufferIndex, - initialSize); - - info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, - buffer, - chan, 0, - (validEnd - validStart) - initialSize); - } + info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, + buffer, + chan, 0, + (validEnd - validStart) - initialSize); } } - - nextPlayPos += info.numSamples; } + + nextPlayPos += info.numSamples; } bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout) { - if (!source || source->getTotalLength() <= 0) + if (source == nullptr || source->getTotalLength() <= 0) return false; - if (nextPlayPos + info.numSamples < 0) + if ((nextPlayPos + info.numSamples < 0) + || (! isLooping() && nextPlayPos > getTotalLength())) return true; - if (! isLooping() && nextPlayPos > getTotalLength()) - return true; - - auto now = Time::getMillisecondCounter(); - auto startTime = now; + const auto startTime = Time::getMillisecondCounter(); + auto now = startTime; auto elapsed = (now >= startTime ? now - startTime : (std::numeric_limits::max() - startTime) + now); while (elapsed <= timeout) { + const auto bufferRange = getValidBufferRange (info.numSamples); + + const auto validStart = bufferRange.getStart(); + const auto validEnd = bufferRange.getEnd(); + + if (validStart <= 0 + && validStart < validEnd + && validEnd >= info.numSamples) { - const ScopedLock sl (bufferStartPosLock); - - auto start = bufferValidStart.load(); - auto end = bufferValidEnd.load(); - auto pos = nextPlayPos.load(); - - auto validStart = static_cast (jlimit (start, end, pos) - pos); - auto validEnd = static_cast (jlimit (start, end, pos + info.numSamples) - pos); - - if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) - return true; + return true; } - if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast (timeout - elapsed)))) + if (elapsed < timeout + && ! bufferReadyEvent.wait (static_cast (timeout - elapsed))) + { return false; + } now = Time::getMillisecondCounter(); elapsed = (now >= startTime ? now - startTime @@ -202,7 +203,7 @@ bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelI int64 BufferingAudioSource::getNextReadPosition() const { jassert (source->getTotalLength() > 0); - auto pos = nextPlayPos.load(); + const auto pos = nextPlayPos.load(); return (source->isLooping() && nextPlayPos > 0) ? pos % source->getTotalLength() @@ -211,18 +212,28 @@ int64 BufferingAudioSource::getNextReadPosition() const void BufferingAudioSource::setNextReadPosition (int64 newPosition) { - const ScopedLock sl (bufferStartPosLock); + const ScopedLock sl (bufferRangeLock); nextPlayPos = newPosition; backgroundThread.moveToFrontOfQueue (this); } +Range BufferingAudioSource::getValidBufferRange (int numSamples) const +{ + const ScopedLock sl (bufferRangeLock); + + const auto pos = nextPlayPos.load(); + + return { (int) (jlimit (bufferValidStart, bufferValidEnd, pos) - pos), + (int) (jlimit (bufferValidStart, bufferValidEnd, pos + numSamples) - pos) }; +} + bool BufferingAudioSource::readNextBufferChunk() { int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; { - const ScopedLock sl (bufferStartPosLock); + const ScopedLock sl (bufferRangeLock); if (wasSourceLooping != isLooping()) { @@ -236,7 +247,7 @@ bool BufferingAudioSource::readNextBufferChunk() sectionToReadStart = 0; sectionToReadEnd = 0; - const int maxChunkSize = 2048; + constexpr int maxChunkSize = 2048; if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) { @@ -257,7 +268,7 @@ bool BufferingAudioSource::readNextBufferChunk() sectionToReadEnd = newBVE; bufferValidStart = newBVS; - bufferValidEnd = jmin (bufferValidEnd.load(), newBVE); + bufferValidEnd = jmin (bufferValidEnd, newBVE); } } @@ -265,8 +276,9 @@ bool BufferingAudioSource::readNextBufferChunk() return false; jassert (buffer.getNumSamples() > 0); - auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); - auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); + + const auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); + const auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); if (bufferIndexStart < bufferIndexEnd) { @@ -276,7 +288,7 @@ bool BufferingAudioSource::readNextBufferChunk() } else { - auto initialSize = buffer.getNumSamples() - bufferIndexStart; + const auto initialSize = buffer.getNumSamples() - bufferIndexStart; readBufferSection (sectionToReadStart, initialSize, @@ -288,7 +300,7 @@ bool BufferingAudioSource::readNextBufferChunk() } { - const ScopedLock sl2 (bufferStartPosLock); + const ScopedLock sl2 (bufferRangeLock); bufferValidStart = newBVS; bufferValidEnd = newBVE; @@ -304,6 +316,8 @@ void BufferingAudioSource::readBufferSection (int64 start, int length, int buffe source->setNextReadPosition (start); AudioSourceChannelInfo info (&buffer, bufferOffset, length); + + const ScopedLock sl (callbackLock); source->getNextAudioBlock (info); } diff --git a/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h b/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h index b87977c587..7bfa24b14e 100644 --- a/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h +++ b/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h @@ -99,20 +99,25 @@ public: private: //============================================================================== - OptionalScopedPointer source; - TimeSliceThread& backgroundThread; - int numberOfSamplesToBuffer, numberOfChannels; - AudioBuffer buffer; - CriticalSection bufferStartPosLock; - WaitableEvent bufferReadyEvent; - std::atomic bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 }; - double sampleRate = 0; - bool wasSourceLooping = false, isPrepared = false, prefillBuffer; - + Range getValidBufferRange (int numSamples) const; bool readNextBufferChunk(); void readBufferSection (int64 start, int length, int bufferOffset); int useTimeSlice() override; + //============================================================================== + OptionalScopedPointer source; + TimeSliceThread& backgroundThread; + int numberOfSamplesToBuffer, numberOfChannels; + AudioBuffer buffer; + CriticalSection callbackLock, bufferRangeLock; + WaitableEvent bufferReadyEvent; + int64 bufferValidStart = 0, bufferValidEnd = 0; + std::atomic nextPlayPos { 0 }; + double sampleRate = 0; + bool wasSourceLooping = false, isPrepared = false; + const bool prefillBuffer; + + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) };