diff --git a/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp b/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp index e0e199d3e7..cb28286d55 100644 --- a/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp +++ b/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp @@ -353,13 +353,14 @@ public: void allocateTempBuffers() { - auto tempBufSize = (size_t) bufferSize + 4; + const auto tempBufSize = (size_t) bufferSize + 4; auto streams = getStreams(); const auto total = std::accumulate (streams.begin(), streams.end(), (size_t) 0, [] (auto n, const auto& s) { return n + (s != nullptr ? s->channels : 0); }); audioBuffer.clear(); audioBuffer.resize (total * tempBufSize); + audioBufferLengthInSamples = (size_t) bufferSize; size_t channels = 0; for (auto* stream : streams) @@ -777,60 +778,52 @@ public: return; } - const auto numInputChans = (int) getChannels (inStream); - const auto numOutputChans = (int) getChannels (outStream); - - if (callback != nullptr) + const auto actualBufferSizeSamples = std::invoke ([&] { - for (int i = numInputChans; --i >= 0;) - { - auto& info = inStream->channelInfo.getReference (i); - auto dest = inStream->tempBuffers[(size_t) i]; - auto src = ((const float*) inInputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples; - auto stride = info.dataStrideSamples; + size_t result = 0; - if (stride != 0) // if this is zero, info is invalid + for (auto [streamPtr, data] : { std::tuple (&inStream, static_cast (inInputData)), + std::tuple (&outStream, static_cast (outOutputData)) }) + { + auto& stream = *streamPtr; + const auto numChannels = (int) getChannels (stream); + + for (auto i = 0; i < numChannels; ++i) { - for (int j = bufferSize; --j >= 0;) - { - *dest++ = *src; - src += stride; - } + const auto info = stream->channelInfo.getReference (i); + const auto stride = (size_t) info.dataStrideSamples; + + if (stride == 0) + continue; + + const auto bufSizeSamples = data->mBuffers[info.streamNum].mDataByteSize / (sizeof (float) * stride); + + // Not all stream buffer sizes are equal! + jassert (result == 0 || result == bufSizeSamples); + + result = bufSizeSamples; } } + return result; + }); + + if (callback != nullptr) + { for (auto* stream : getStreams()) - if (stream != nullptr) - owner.hadDiscontinuity |= stream->checkTimestampsForDiscontinuity (stream == inStream.get() ? inputTimestamp - : outputTimestamp); - - const auto* timeStamp = numOutputChans > 0 ? outputTimestamp : inputTimestamp; - const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0; - const AudioIODeviceCallbackContext context { - timeStamp != nullptr ? &nanos : nullptr, - }; + if (stream == nullptr) + continue; - callback->audioDeviceIOCallbackWithContext (getTempBuffers (inStream), numInputChans, - getTempBuffers (outStream), numOutputChans, - bufferSize, - context); + const auto timeStamp = stream == inStream.get() ? inputTimestamp : outputTimestamp; + owner.hadDiscontinuity |= stream->checkTimestampsForDiscontinuity (timeStamp); + } - for (int i = numOutputChans; --i >= 0;) + for (size_t offset = 0; offset < actualBufferSizeSamples;) { - auto& info = outStream->channelInfo.getReference (i); - auto src = outStream->tempBuffers[(size_t) i]; - auto dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples; - auto stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest = *src++; - dest += stride; - } - } + const auto numSamplesInChunk = jmin (actualBufferSizeSamples - offset, audioBufferLengthInSamples); + processBufferChunk (offset, numSamplesInChunk, inputTimestamp, outputTimestamp, inInputData, outOutputData); + offset += numSamplesInChunk; } } else @@ -842,7 +835,7 @@ public: for (auto* stream : getStreams()) if (stream != nullptr) - stream->previousSampleTime += static_cast (bufferSize); + stream->previousSampleTime += static_cast (actualBufferSizeSamples); } // called by callbacks (possibly off the main thread) @@ -1065,6 +1058,111 @@ public: AudioWorkgroup audioWorkgroup; private: + template + struct StrideIterator + { + StrideIterator (Iterator iteratorIn, ptrdiff_t strideIn) + : iterator (std::move (iteratorIn)), stride (strideIn) {} + + StrideIterator& operator++() + { + iterator += stride; + return *this; + } + + StrideIterator operator++ (int) + { + auto copy = *this; + operator++(); + return copy; + } + + // decltype (auto) here because the return types may be references + decltype (auto) operator* () const { return *iterator; } + + bool operator== (const StrideIterator& other) const { return iterator == other.iterator; } + bool operator!= (const StrideIterator& other) const { return iterator != other.iterator; } + + StrideIterator& operator+= (ptrdiff_t x) + { + iterator += stride * x; + return *this; + } + + StrideIterator operator+ (ptrdiff_t x) const + { + return StrideIterator { *this } += x; + } + + Iterator iterator; + ptrdiff_t stride; + }; + + void processBufferChunk (size_t sampleOffset, + size_t numSamplesInChunk, + const AudioTimeStamp* inputTimestamp, + const AudioTimeStamp* outputTimestamp, + const AudioBufferList* inInputData, + AudioBufferList* outOutputData) + { + // precondition + jassert (callback != nullptr); + + const auto numInputChans = (int) getChannels (inStream); + const auto numOutputChans = (int) getChannels (outStream); + + // copy from input buffer to temporary buffer + for (auto index = 0; index < numInputChans; ++index) + { + const auto info = inStream->channelInfo.getReference (index); + const auto src = StrideIterator { ((const float*) inInputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples, + info.dataStrideSamples } + + (ptrdiff_t) sampleOffset; + + if (src.stride == 0) // if this is zero, info is invalid + continue; + + std::copy (src, src + (ptrdiff_t) numSamplesInChunk, inStream->tempBuffers[(size_t) index]); + } + + // only pass a timestamp for the first chunk of each buffer + const auto* timeStamp = std::invoke ([&]() -> const AudioTimeStamp* + { + if (sampleOffset != 0) + return nullptr; + + return numOutputChans > 0 ? outputTimestamp : inputTimestamp; + }); + + const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0; + const AudioIODeviceCallbackContext context + { + timeStamp != nullptr ? &nanos : nullptr, + }; + + callback->audioDeviceIOCallbackWithContext (getTempBuffers (inStream), + numInputChans, + getTempBuffers (outStream), + numOutputChans, + (int) numSamplesInChunk, + context); + + // copy from temporary buffer to output buffer + for (auto index = 0; index < numOutputChans; ++index) + { + const auto info = outStream->channelInfo.getReference (index); + const auto dest = StrideIterator { ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples, + info.dataStrideSamples } + + (ptrdiff_t) sampleOffset; + + if (dest.stride == 0) // if this is zero, info is invalid + continue; + + const auto* src = outStream->tempBuffers[(size_t) index]; + std::copy (src, src + numSamplesInChunk, dest); + } + } + class ScopedAudioDeviceIOProcID { public: @@ -1117,6 +1215,7 @@ private: double sampleRate = 0; int bufferSize = 0; std::vector audioBuffer; + size_t audioBufferLengthInSamples = 0; Atomic callbacksAllowed { 1 }; //==============================================================================