diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 10f3a9bbca..8e633c2bb8 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -31,6 +31,8 @@ #include "juce_audio_basics.h" +#include + #if JUCE_MINGW && ! defined (alloca) #define alloca __builtin_alloca #endif diff --git a/modules/juce_audio_basics/juce_audio_basics.h b/modules/juce_audio_basics/juce_audio_basics.h index 7bd19b558f..62ccbc38cc 100644 --- a/modules/juce_audio_basics/juce_audio_basics.h +++ b/modules/juce_audio_basics/juce_audio_basics.h @@ -120,4 +120,4 @@ #include "sources/juce_ReverbAudioSource.h" #include "sources/juce_ToneGeneratorAudioSource.h" #include "synthesisers/juce_Synthesiser.h" -#include "audio_play_head/juce_AudioPlayHead.h" +#include "audio_play_head/juce_AudioPlayHead.h" \ No newline at end of file diff --git a/modules/juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h b/modules/juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h new file mode 100644 index 0000000000..2c8b64eefc --- /dev/null +++ b/modules/juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h @@ -0,0 +1,80 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +// This file will be included directly by macOS/iOS-specific .cpps +#pragma once + +#if ! DOXYGEN + +#include + +namespace juce +{ + +struct CoreAudioTimeConversions +{ +public: + CoreAudioTimeConversions() + { + mach_timebase_info_data_t info{}; + mach_timebase_info (&info); + numerator = info.numer; + denominator = info.denom; + } + + uint64_t hostTimeToNanos (uint64_t hostTime) const + { + return multiplyByRatio (hostTime, numerator, denominator); + } + + uint64_t nanosToHostTime (uint64_t nanos) const + { + return multiplyByRatio (nanos, denominator, numerator); + } + +private: + // Adapted from CAHostTimeBase.h in the Core Audio Utility Classes + static uint64_t multiplyByRatio (uint64_t toMultiply, uint64_t numerator, uint64_t denominator) + { + #if defined (__SIZEOF_INT128__) + unsigned __int128 + #else + long double + #endif + result = toMultiply; + + if (numerator != denominator) + { + result *= numerator; + result /= denominator; + } + + return (uint64_t) result; + } + + uint64_t numerator = 0, denominator = 0; +}; + +} // namespace juce + +#endif diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 80c16e07be..d1beeb9688 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -69,9 +69,14 @@ public: CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} private: - void audioDeviceIOCallback (const float** ins, int numIns, float** outs, int numOuts, int numSamples) override + void audioDeviceIOCallbackWithContext (const float** ins, + int numIns, + float** outs, + int numOuts, + int numSamples, + const AudioIODeviceCallbackContext& context) override { - owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples); + owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples, context); } void audioDeviceAboutToStart (AudioIODevice* device) override @@ -900,7 +905,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat int numInputChannels, float** outputChannelData, int numOutputChannels, - int numSamples) + int numSamples, + const AudioIODeviceCallbackContext& context) { const ScopedLock sl (audioCallbackLock); @@ -912,15 +918,23 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); - callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, - outputChannelData, numOutputChannels, numSamples); + callbacks.getUnchecked(0)->audioDeviceIOCallbackWithContext (inputChannelData, + numInputChannels, + outputChannelData, + numOutputChannels, + numSamples, + context); auto** tempChans = tempBuffer.getArrayOfWritePointers(); for (int i = callbacks.size(); --i > 0;) { - callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, - tempChans, numOutputChannels, numSamples); + callbacks.getUnchecked(i)->audioDeviceIOCallbackWithContext (inputChannelData, + numInputChannels, + tempChans, + numOutputChannels, + numSamples, + context); for (int chan = 0; chan < numOutputChannels; ++chan) { diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index bed2ef585f..f288b6e14b 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -526,8 +526,12 @@ private: class CallbackHandler; std::unique_ptr callbackHandler; - void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels, - float** outputChannelData, int totalNumOutputChannels, int numSamples); + void audioDeviceIOCallbackInt (const float** inputChannelData, + int totalNumInputChannels, + float** outputChannelData, + int totalNumOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext& context); void audioDeviceAboutToStartInt (AudioIODevice*); void audioDeviceStoppedInt(); void audioDeviceErrorInt (const String&); diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h index e91e9d98f3..8864ffd783 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h @@ -25,6 +25,14 @@ namespace juce class AudioIODevice; +/** Additional information that may be passed to the AudioIODeviceCallback. */ +struct AudioIODeviceCallbackContext +{ + /** If the host provides this information, this field will be set to point to + an integer holding the current value; otherwise, this will be nullptr. + */ + const uint64_t* hostTimeNs = nullptr; +}; //============================================================================== /** @@ -87,7 +95,26 @@ public: int numInputChannels, float** outputChannelData, int numOutputChannels, - int numSamples) = 0; + int numSamples) + { + ignoreUnused (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); + } + + /** The same as audioDeviceIOCallback(), but with an additional context argument. + + The default implementation of this function will call audioDeviceIOCallback(), + but you can override this function if you need to make use of the context information. + */ + virtual void audioDeviceIOCallbackWithContext (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext& context) + { + audioDeviceIOCallback (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); + ignoreUnused (context); + } /** Called to indicate that the device is about to start calling back. diff --git a/modules/juce_audio_devices/native/juce_android_Audio.cpp b/modules/juce_audio_devices/native/juce_android_Audio.cpp index 002940786b..75607c6916 100644 --- a/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -357,9 +357,11 @@ public: if (callback != nullptr) { - callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels, - outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels, - actualBufferSize); + callback->audioDeviceIOCallbackWithContext (inputChannelBuffer.getArrayOfReadPointers(), + numClientInputChannels, + outputChannelBuffer.getArrayOfWritePointers(), + numClientOutputChannels, + actualBufferSize, {}); } else { diff --git a/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/modules/juce_audio_devices/native/juce_android_Oboe.cpp index 2e9ec45eb7..105a182a2f 100644 --- a/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ b/modules/juce_audio_devices/native/juce_android_Oboe.cpp @@ -428,8 +428,12 @@ private: { if (auto* cb = callback.exchange (nullptr)) { - cb->audioDeviceIOCallback (inputChannelData, numInputChannels, - outputChannelData, numOutputChannels, numFrames); + cb->audioDeviceIOCallbackWithContext (inputChannelData, + numInputChannels, + outputChannelData, + numOutputChannels, + numFrames, + {}); callback.set (cb); } else diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index cc7d8a50ff..674f827e70 100644 --- a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -605,7 +605,7 @@ public: { if (auto* cb = callback.exchange (nullptr)) { - cb->audioDeviceIOCallback (inputChannelData, inputChannels, outputChannelData, outputChannels, bufferSize); + cb->audioDeviceIOCallbackWithContext (inputChannelData, inputChannels, outputChannelData, outputChannels, bufferSize, {}); callback.set (cb); } else diff --git a/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 592c033440..2e162685e9 100644 --- a/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -20,6 +20,8 @@ ============================================================================== */ +#include + namespace juce { @@ -900,9 +902,14 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, zeromem (inputData[c], channelDataSize); } - callback->audioDeviceIOCallback ((const float**) inputData, channelData.inputs ->numActiveChannels, - outputData, channelData.outputs->numActiveChannels, - (int) numFrames); + const auto nanos = time != nullptr ? timeConversions.hostTimeToNanos (time->mHostTime) : 0; + + callback->audioDeviceIOCallbackWithContext ((const float**) inputData, + channelData.inputs ->numActiveChannels, + outputData, + channelData.outputs->numActiveChannels, + (int) numFrames, + { (time != nullptr && (time->mFlags & kAudioTimeStampHostTimeValid) != 0) ? &nanos : nullptr }); for (int c = 0; c < channelData.outputs->numActiveChannels; ++c) { @@ -1329,6 +1336,8 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, AudioBuffer audioData { 0, 0 }; }; + CoreAudioTimeConversions timeConversions; + IOChannelData channelData; BigInteger requestedInputChannels, requestedOutputChannels; diff --git a/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/modules/juce_audio_devices/native/juce_linux_ALSA.cpp index 2fb9f8430f..dafb34cf95 100644 --- a/modules/juce_audio_devices/native/juce_linux_ALSA.cpp +++ b/modules/juce_audio_devices/native/juce_linux_ALSA.cpp @@ -711,11 +711,12 @@ public: if (callback != nullptr) { - callback->audioDeviceIOCallback (inputChannelDataForCallback.getRawDataPointer(), - inputChannelDataForCallback.size(), - outputChannelDataForCallback.getRawDataPointer(), - outputChannelDataForCallback.size(), - bufferSize); + callback->audioDeviceIOCallbackWithContext (inputChannelDataForCallback.getRawDataPointer(), + inputChannelDataForCallback.size(), + outputChannelDataForCallback.getRawDataPointer(), + outputChannelDataForCallback.size(), + bufferSize, + {}); } else { diff --git a/modules/juce_audio_devices/native/juce_linux_Bela.cpp b/modules/juce_audio_devices/native/juce_linux_Bela.cpp index e1917591de..bf1ff16f39 100644 --- a/modules/juce_audio_devices/native/juce_linux_Bela.cpp +++ b/modules/juce_audio_devices/native/juce_linux_Bela.cpp @@ -433,9 +433,12 @@ private: channelOutBuffer[ch] = &context.analogOut[(Frames) (ch - analogChannelStart) * context.audioFrames]; } - callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, - channelOutBuffer.getData(), actualNumberOfOutputs, - (int) context.audioFrames); + callback->audioDeviceIOCallbackWithContext (channelInBuffer.getData(), + actualNumberOfInputs, + channelOutBuffer.getData(), + actualNumberOfOutputs, + (int) context.audioFrames, + {}); } } diff --git a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp index 98b35d25b6..8b7e77573b 100644 --- a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp @@ -462,8 +462,12 @@ private: if (callback != nullptr) { if ((numActiveInChans + numActiveOutChans) > 0) - callback->audioDeviceIOCallback (const_cast (inChans.getData()), numActiveInChans, - outChans, numActiveOutChans, numSamples); + callback->audioDeviceIOCallbackWithContext (const_cast (inChans.getData()), + numActiveInChans, + outChans, + numActiveOutChans, + numSamples, + {}); } else { diff --git a/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index bbe72577a1..b9dfa3e972 100644 --- a/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -20,6 +20,8 @@ ============================================================================== */ +#include + namespace juce { @@ -746,7 +748,8 @@ public: double getSampleRate() const { return sampleRate; } int getBufferSize() const { return bufferSize; } - void audioCallback (const AudioBufferList* inInputData, + void audioCallback (const AudioTimeStamp* timeStamp, + const AudioBufferList* inInputData, AudioBufferList* outOutputData) { const ScopedLock sl (callbackLock); @@ -778,11 +781,14 @@ public: } } - callback->audioDeviceIOCallback (const_cast (tempInputBuffers.get()), - numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); + const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0; + + callback->audioDeviceIOCallbackWithContext (const_cast (tempInputBuffers.get()), + numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize, + { timeStamp != nullptr ? &nanos : nullptr }); for (int i = numOutputChans; --i >= 0;) { @@ -838,6 +844,7 @@ public: AudioDeviceIOProcID audioProcID = {}; private: + CoreAudioTimeConversions timeConversions; AudioIODeviceCallback* callback = nullptr; CriticalSection callbackLock; AudioDeviceID deviceID; @@ -876,14 +883,14 @@ private: } static OSStatus audioIOProc (AudioDeviceID /*inDevice*/, - const AudioTimeStamp* /*inNow*/, + const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* /*inInputTime*/, AudioBufferList* outOutputData, const AudioTimeStamp* /*inOutputTime*/, void* device) { - static_cast (device)->audioCallback (inInputData, outOutputData); + static_cast (device)->audioCallback (inNow, inInputData, outOutputData); return noErr; } @@ -1624,8 +1631,12 @@ private: const ScopedLock sl (callbackLock); if (callback != nullptr) - callback->audioDeviceIOCallback ((const float**) inputChans.getRawDataPointer(), numInputChans, - outputChans.getRawDataPointer(), numOutputChans, numSamples); + callback->audioDeviceIOCallbackWithContext ((const float**) inputChans.getRawDataPointer(), + numInputChans, + outputChans.getRawDataPointer(), + numOutputChans, + numSamples, + {}); // Can't predict when the next output callback will happen else didCallback = false; } @@ -1920,9 +1931,12 @@ private: outputFifo.finishedWrite (size1 + size2); } - void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, - float** outputChannelData, int numOutputChannels, - int numSamples) override + void audioDeviceIOCallbackWithContext (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext&) override { if (numInputChannels > 0) { diff --git a/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/modules/juce_audio_devices/native/juce_win32_ASIO.cpp index bd72a9c3e8..529c8b6f29 100644 --- a/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ b/modules/juce_audio_devices/native/juce_win32_ASIO.cpp @@ -1326,8 +1326,12 @@ private: inputFormat[i].convertToFloat (infos[i].buffers[bufferIndex], inBuffers[i], samps); } - currentCallback->audioDeviceIOCallback (const_cast (inBuffers.getData()), numActiveInputChans, - outBuffers, numActiveOutputChans, samps); + currentCallback->audioDeviceIOCallbackWithContext (const_cast (inBuffers.getData()), + numActiveInputChans, + outBuffers, + numActiveOutputChans, + samps, + {}); for (int i = 0; i < numActiveOutputChans; ++i) { diff --git a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp index 9a9b2fc2c8..b0e5dcd1b5 100644 --- a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp @@ -1016,9 +1016,12 @@ public: if (isStarted) { - callback->audioDeviceIOCallback (inputBuffers.getArrayOfReadPointers(), inputBuffers.getNumChannels(), - outputBuffers.getArrayOfWritePointers(), outputBuffers.getNumChannels(), - bufferSizeSamples); + callback->audioDeviceIOCallbackWithContext (inputBuffers.getArrayOfReadPointers(), + inputBuffers.getNumChannels(), + outputBuffers.getArrayOfWritePointers(), + outputBuffers.getNumChannels(), + bufferSizeSamples, + {}); } else { diff --git a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index c5c5307d89..b1a0aae158 100644 --- a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -1515,8 +1515,12 @@ public: const ScopedTryLock sl (startStopLock); if (sl.isLocked() && isStarted) - callback->audioDeviceIOCallback (const_cast (inputBuffers), numInputBuffers, - outputBuffers, numOutputBuffers, bufferSize); + callback->audioDeviceIOCallbackWithContext (const_cast (inputBuffers), + numInputBuffers, + outputBuffers, + numOutputBuffers, + bufferSize, + {}); else outs.clear(); } diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 04219eb333..752838eaf2 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -77,6 +77,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE #include "../utility/juce_CarbonVisibility.h" #include +#include #include #include @@ -1287,6 +1288,22 @@ public: { lastTimeStamp = inTimeStamp; + jassert (! juceFilter->getHostTimeNs()); + + if ((inTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) + { + const auto timestamp = timeConversions.hostTimeToNanos (inTimeStamp.mHostTime); + juceFilter->setHostTimeNanos (×tamp); + } + + struct AtEndOfScope + { + ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + AudioProcessor& proc; + }; + + const AtEndOfScope scope { *juceFilter }; + // prepare buffers { pullInputAudio (ioActionFlags, inTimeStamp, nFrames); @@ -1816,6 +1833,7 @@ private: // According to the docs, this is the maximum size of a MIDIPacketList. static constexpr UInt32 packetListBytes = 65536; + CoreAudioTimeConversions timeConversions; AudioUnitEvent auEvent; mutable Array presetsArray; CriticalSection incomingMidiLock; diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index d2028e3bcb..f3499bfafe 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -51,6 +51,7 @@ #include #include +#include #include #include @@ -1521,6 +1522,23 @@ private: const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); + if (timestamp != nullptr) + { + if ((timestamp->mFlags & kAudioTimeStampHostTimeValid) != 0) + { + const auto convertedTime = timeConversions.hostTimeToNanos (timestamp->mHostTime); + getAudioProcessor().setHostTimeNanos (&convertedTime); + } + } + + struct AtEndOfScope + { + ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + AudioProcessor& proc; + }; + + const AtEndOfScope scope { getAudioProcessor() }; + if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) { // process params and incoming midi (only once for a given timestamp) @@ -1764,6 +1782,7 @@ private: int totalInChannels, totalOutChannels; + CoreAudioTimeConversions timeConversions; std::unique_ptr inputBusses, outputBusses; ObjCBlock paramObserver; diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index b7f3a04104..0e843d82e0 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -441,11 +441,12 @@ private: inner.audioDeviceAboutToStart (device); } - void audioDeviceIOCallback (const float** inputChannelData, - int numInputChannels, - float** outputChannelData, - int numOutputChannels, - int numSamples) override + void audioDeviceIOCallbackWithContext (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext& context) override { jassertquiet ((int) storedInputChannels.size() == numInputChannels); jassertquiet ((int) storedOutputChannels.size() == numOutputChannels); @@ -459,11 +460,12 @@ private: initChannelPointers (inputChannelData, storedInputChannels, position); initChannelPointers (outputChannelData, storedOutputChannels, position); - inner.audioDeviceIOCallback (storedInputChannels.data(), - (int) storedInputChannels.size(), - storedOutputChannels.data(), - (int) storedOutputChannels.size(), - blockLength); + inner.audioDeviceIOCallbackWithContext (storedInputChannels.data(), + (int) storedInputChannels.size(), + storedOutputChannels.data(), + (int) storedOutputChannels.size(), + blockLength, + context); position += blockLength; } @@ -591,11 +593,12 @@ private: }; //============================================================================== - void audioDeviceIOCallback (const float** inputChannelData, - int numInputChannels, - float** outputChannelData, - int numOutputChannels, - int numSamples) override + void audioDeviceIOCallbackWithContext (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples, + const AudioIODeviceCallbackContext& context) override { if (muteInput) { @@ -603,8 +606,12 @@ private: inputChannelData = emptyBuffer.getArrayOfReadPointers(); } - player.audioDeviceIOCallback (inputChannelData, numInputChannels, - outputChannelData, numOutputChannels, numSamples); + player.audioDeviceIOCallbackWithContext (inputChannelData, + numInputChannels, + outputChannelData, + numOutputChannels, + numSamples, + context); } void audioDeviceAboutToStart (AudioIODevice* device) override diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 2bb9bfb08c..7ac4a66e14 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "../utility/juce_CheckSettingMacros.h" #if JucePlugin_Build_VST @@ -354,7 +355,7 @@ public: jassert (isProcessing); // (tragically, some hosts actually need this, although it's stupid to have - // to do it here..) + // to do it here.) if (! isProcessing) resume(); @@ -389,6 +390,16 @@ public: } else { + updateCallbackContextInfo(); + + struct AtEndOfScope + { + ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + AudioProcessor& proc; + }; + + const AtEndOfScope scope { *processor }; + int i; for (i = 0; i < numOut; ++i) { @@ -589,25 +600,28 @@ public: } } - //============================================================================== - bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override + void updateCallbackContextInfo() { const Vst2::VstTimeInfo* ti = nullptr; if (hostCallback != nullptr) { int32 flags = Vst2::kVstPpqPosValid | Vst2::kVstTempoValid - | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid - | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid - | Vst2::kVstClockValid; + | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid + | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid + | Vst2::kVstClockValid | Vst2::kVstNanosValid; auto result = hostCallback (&vstEffect, Vst2::audioMasterGetTime, 0, flags, nullptr, 0); ti = reinterpret_cast (result); } if (ti == nullptr || ti->sampleRate <= 0) - return false; + { + currentPosition.reset(); + return; + } + auto& info = currentPosition.emplace(); info.bpm = (ti->flags & Vst2::kVstTempoValid) != 0 ? ti->tempo : 0.0; if ((ti->flags & Vst2::kVstTimeSigValid) != 0) @@ -675,6 +689,20 @@ public: info.ppqLoopEnd = 0; } + if ((ti->flags & Vst2::kVstNanosValid) != 0) + { + const auto nanos = (uint64_t) ti->nanoSeconds; + processor->setHostTimeNanos (&nanos); + } + } + + //============================================================================== + bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override + { + if (! currentPosition.hasValue()) + return false; + + info = *currentPosition; return true; } @@ -2068,6 +2096,7 @@ private: Vst2::ERect editorRect; MidiBuffer midiEvents; VSTMidiEventList outgoingEvents; + Optional currentPosition; LegacyAudioParametersWrapper juceParameters; diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index ef9ba9d5f4..a727a92bdb 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -3170,6 +3170,12 @@ public: { processContext = *data.processContext; + if ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0) + { + const auto timestamp = (uint64_t) processContext.systemTime; + getPluginInstance().setHostTimeNanos (×tamp); + } + if (juceVST3EditController != nullptr) juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; } @@ -3181,6 +3187,14 @@ public: juceVST3EditController->vst3IsPlaying = false; } + struct AtEndOfScope + { + ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + AudioProcessor& proc; + }; + + const AtEndOfScope scope { getPluginInstance() }; + midiBuffer.clear(); if (data.inputParameterChanges != nullptr) diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index b77827c673..b172ca7574 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -34,6 +34,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") #include +#include #include #include #include "juce_AU_Shared.h" @@ -332,39 +333,25 @@ namespace AudioUnitFormatHelpers #endif template - struct BasicOptional - { - BasicOptional() = default; - - explicit constexpr BasicOptional (Value&& v) : value (std::move (v)), isValid (true) {} - explicit constexpr BasicOptional (const Value& v) : value (v), isValid (true) {} - - explicit constexpr operator bool() const noexcept { return isValid; } - - Value value; - bool isValid { false }; - }; - - template - static BasicOptional tryGetProperty (AudioUnit inUnit, - AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement) + static Optional tryGetProperty (AudioUnit inUnit, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement) { Value data; auto size = (UInt32) sizeof (Value); if (AudioUnitGetProperty (inUnit, inID, inScope, inElement, &data, &size) == noErr) - return BasicOptional (data); + return data; - return BasicOptional(); + return {}; } static UInt32 getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept { const auto count = tryGetProperty (comp, kAudioUnitProperty_ElementCount, scope, 0); - jassert (count.isValid); - return count.value; + jassert (count); + return *count; } /* The plugin may expect its channels in a particular order, reported to the host @@ -390,8 +377,8 @@ namespace AudioUnitFormatHelpers if (const auto layout = tryGetProperty (comp, kAudioUnitProperty_AudioChannelLayout, scope, busIndex)) { - const auto juceChannelOrder = CoreAudioLayouts::fromCoreAudio (layout.value); - const auto auChannelOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (layout.value); + const auto juceChannelOrder = CoreAudioLayouts::fromCoreAudio (*layout); + const auto auChannelOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (*layout); for (auto juceChannelIndex = 0; juceChannelIndex < juceChannelOrder.size(); ++juceChannelIndex) busMap.push_back ((size_t) auChannelOrder.indexOf (juceChannelOrder.getTypeOfChannel (juceChannelIndex))); @@ -1090,7 +1077,7 @@ public: zerostruct (timeStamp); timeStamp.mSampleTime = 0; - timeStamp.mHostTime = GetCurrentHostTime (0, newSampleRate, isAUv3); + timeStamp.mHostTime = mach_absolute_time(); timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampHostTimeValid; wasPlaying = false; @@ -1148,6 +1135,17 @@ public: void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) { + if (const auto* hostTimeNs = getHostTimeNs()) + { + timeStamp.mHostTime = *hostTimeNs; + timeStamp.mFlags |= kAudioTimeStampHostTimeValid; + } + else + { + timeStamp.mHostTime = 0; + timeStamp.mFlags &= ~kAudioTimeStampHostTimeValid; + } + // If these are hit, we might allocate in the process block! jassert (buffer.getNumChannels() <= preparedChannels); jassert (buffer.getNumSamples() <= preparedSamples); @@ -1156,7 +1154,7 @@ public: // to the following bus. inputBuffer.makeCopyOf (buffer, true); - auto numSamples = buffer.getNumSamples(); + const auto numSamples = buffer.getNumSamples(); if (auSupportsBypass) { @@ -1170,8 +1168,6 @@ public: if (prepared) { - timeStamp.mHostTime = GetCurrentHostTime (numSamples, getSampleRate(), isAUv3); - const auto numOutputBuses = getBusCount (false); for (int i = 0; i < numOutputBuses; ++i) @@ -1615,6 +1611,8 @@ private: friend class AudioUnitPluginWindowCocoa; friend class AudioUnitPluginFormat; + CoreAudioTimeConversions timeConversions; + AudioComponentDescription componentDesc; AudioComponent auComponent; String pluginName, manufacturer, version; @@ -2164,29 +2162,6 @@ private: } //============================================================================== - static UInt64 GetCurrentHostTime (int numSamples, double sampleRate, bool isAUv3) noexcept - { - #if ! JUCE_IOS - if (! isAUv3) - return AudioGetCurrentHostTime(); - #else - ignoreUnused (isAUv3); - #endif - - UInt64 currentTime = mach_absolute_time(); - static mach_timebase_info_data_t sTimebaseInfo = { 0, 0 }; - - if (sTimebaseInfo.denom == 0) - mach_timebase_info (&sTimebaseInfo); - - auto bufferNanos = static_cast (numSamples) * 1.0e9 / sampleRate; - auto bufferTicks = static_cast (std::ceil (bufferNanos * (static_cast (sTimebaseInfo.denom) - / static_cast (sTimebaseInfo.numer)))); - currentTime += bufferTicks; - - return currentTime; - } - bool isBusCountWritable (bool isInput) const noexcept { UInt32 countSize; diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 8f72c3f9f7..d86da47349 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -20,6 +20,8 @@ #ifndef DOXYGEN +#include + namespace juce { @@ -1033,10 +1035,8 @@ public: if (eventList.getEvent (i, e) != Steinberg::kResultOk) continue; - const auto message = toMidiMessage (e); - - if (message.isValid) - result.addEvent (message.item, e.sampleOffset); + if (const auto message = toMidiMessage (e)) + result.addEvent (*message, e.sampleOffset); } } @@ -1109,15 +1109,12 @@ private: } } - auto maybeEvent = createVstEvent (msg, metadata.data, kind); - - if (! maybeEvent.isValid) - continue; - - auto& e = maybeEvent.item; - e.busIndex = 0; - e.sampleOffset = metadata.samplePosition; - result.addEvent (e); + if (auto maybeEvent = createVstEvent (msg, metadata.data, kind)) + { + maybeEvent->busIndex = 0; + maybeEvent->sampleOffset = metadata.samplePosition; + result.addEvent (*maybeEvent); + } } } @@ -1234,19 +1231,9 @@ private: msg.getQuarterFrameValue()); } - template - struct BasicOptional final - { - BasicOptional() noexcept = default; - BasicOptional (const Item& i) noexcept : item { i }, isValid { true } {} - - Item item; - bool isValid{}; - }; - - static BasicOptional createVstEvent (const MidiMessage& msg, - const uint8* midiEventData, - EventConversionKind kind) noexcept + static Optional createVstEvent (const MidiMessage& msg, + const uint8* midiEventData, + EventConversionKind kind) noexcept { if (msg.isNoteOn()) return createNoteOnEvent (msg); @@ -1290,7 +1277,7 @@ private: return {}; } - static BasicOptional toMidiMessage (const Steinberg::Vst::LegacyMIDICCOutEvent& e) + static Optional toMidiMessage (const Steinberg::Vst::LegacyMIDICCOutEvent& e) { if (e.controlNumber <= 127) return MidiMessage::controllerEvent (createSafeChannel (int16 (e.channel)), @@ -1327,7 +1314,7 @@ private: } } - static BasicOptional toMidiMessage (const Steinberg::Vst::Event& e) + static Optional toMidiMessage (const Steinberg::Vst::Event& e) { switch (e.type) { diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index ee49e946de..0b7b50a7f3 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -240,7 +240,10 @@ static void setStateForAllBusesOfType (Vst::IComponent* component, } //============================================================================== -static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, double sampleRate) +static void toProcessContext (Vst::ProcessContext& context, + AudioPlayHead* playHead, + double sampleRate, + const uint64_t* hostTimeNs) { jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK @@ -295,6 +298,13 @@ static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playH if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) context.state |= ProcessContext::kTimeSigValid; + + if (hostTimeNs != nullptr) + { + context.systemTime = (int64_t) *hostTimeNs; + jassert (context.systemTime >= 0); + context.state |= ProcessContext::kSystemTimeValid; + } } //============================================================================== @@ -3345,7 +3355,7 @@ private: void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) { - toProcessContext (timingInfo, getPlayHead(), processSampleRate); + toProcessContext (timingInfo, getPlayHead(), processSampleRate, getHostTimeNs()); destination.processContext = &timingInfo; } diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index e4897c220a..b0a050a33e 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -2367,7 +2367,6 @@ private: if (currentPlayHead->getCurrentPosition (position)) { - vstHostTime.samplePos = (double) position.timeInSamples; vstHostTime.tempo = position.bpm; vstHostTime.timeSigNumerator = position.timeSigNumerator; @@ -2375,9 +2374,19 @@ private: vstHostTime.ppqPos = position.ppqPosition; vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; vstHostTime.flags |= Vst2::kVstTempoValid - | Vst2::kVstTimeSigValid - | Vst2::kVstPpqPosValid - | Vst2::kVstBarsValid; + | Vst2::kVstTimeSigValid + | Vst2::kVstPpqPosValid + | Vst2::kVstBarsValid; + + if (const auto* hostTimeNs = getHostTimeNs()) + { + vstHostTime.nanoSeconds = (double) *hostTimeNs; + vstHostTime.flags |= Vst2::kVstNanosValid; + } + else + { + vstHostTime.flags &= ~Vst2::kVstNanosValid; + } int32 newTransportFlags = 0; if (position.isPlaying) newTransportFlags |= Vst2::kVstTransportPlaying; @@ -2389,28 +2398,22 @@ private: else vstHostTime.flags &= ~Vst2::kVstTransportChanged; - struct OptionalFrameRate - { - bool valid; - Vst2::VstInt32 rate; - }; - - const auto optionalFrameRate = [&fr = position.frameRate]() -> OptionalFrameRate + const auto optionalFrameRate = [&fr = position.frameRate]() -> Optional { switch (fr.getBaseRate()) { - case 24: return { true, fr.isPullDown() ? Vst2::kVstSmpte239fps : Vst2::kVstSmpte24fps }; - case 25: return { true, fr.isPullDown() ? Vst2::kVstSmpte249fps : Vst2::kVstSmpte25fps }; - case 30: return { true, fr.isPullDown() ? (fr.isDrop() ? Vst2::kVstSmpte2997dfps : Vst2::kVstSmpte2997fps) - : (fr.isDrop() ? Vst2::kVstSmpte30dfps : Vst2::kVstSmpte30fps) }; - case 60: return { true, fr.isPullDown() ? Vst2::kVstSmpte599fps : Vst2::kVstSmpte60fps }; + case 24: return fr.isPullDown() ? Vst2::kVstSmpte239fps : Vst2::kVstSmpte24fps; + case 25: return fr.isPullDown() ? Vst2::kVstSmpte249fps : Vst2::kVstSmpte25fps; + case 30: return fr.isPullDown() ? (fr.isDrop() ? Vst2::kVstSmpte2997dfps : Vst2::kVstSmpte2997fps) + : (fr.isDrop() ? Vst2::kVstSmpte30dfps : Vst2::kVstSmpte30fps); + case 60: return fr.isPullDown() ? Vst2::kVstSmpte599fps : Vst2::kVstSmpte60fps; } - return { false, Vst2::VstSmpteFrameRate{} }; + return {}; }(); - vstHostTime.flags |= optionalFrameRate.valid ? Vst2::kVstSmpteValid : 0; - vstHostTime.smpteFrameRate = optionalFrameRate.rate; + vstHostTime.flags |= optionalFrameRate ? Vst2::kVstSmpteValid : 0; + vstHostTime.smpteFrameRate = optionalFrameRate.orFallback (Vst2::VstSmpteFrameRate{}); vstHostTime.smpteOffset = (int32) (position.timeInSeconds * 80.0 * position.frameRate.getEffectiveRate() + 0.5); if (position.isLooping) diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index f85d308282..f018f5f672 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -33,6 +33,7 @@ #include "juce_audio_processors.h" #include +#include //============================================================================== #if JUCE_MAC diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 01db905dc3..639f6522b2 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1125,6 +1125,51 @@ public: */ virtual void setPlayHead (AudioPlayHead* newPlayHead); + //============================================================================== + /** Hosts may call this function to supply the system time corresponding to the + current audio buffer. + + If you want to set a valid time, pass a pointer to a uint64_t holding the current time. The + value will be copied into the AudioProcessor instance without any allocation/deallocation. + + If you want to clear any stored host time, pass nullptr. + + Calls to this function must be synchronised (i.e. not simultaneous) with the audio callback. + + @code + const auto currentHostTime = computeHostTimeNanos(); + processor.setHostTimeNanos (¤tHostTime); // Set a valid host time + // ...call processBlock etc. + processor.setHostTimeNanos (nullptr); // Clear host time + @endcode + */ + void setHostTimeNanos (const uint64_t* hostTimeIn) + { + hasHostTime = hostTimeIn != nullptr; + hostTime = hasHostTime ? *hostTimeIn : 0; + } + + /** The plugin may call this function inside the processBlock function (and only there!) + to find the timestamp associated with the current audio block. + + If a timestamp is available, this will return a pointer to that timestamp. You should + immediately copy the pointed-to value and use that in any following code. Do *not* free + any pointer returned by this function. + + If no timestamp is provided, this will return nullptr. + + @code + void processBlock (AudioBuffer&, MidiBuffer&) override + { + if (auto* timestamp = getHostTimeNs()) + { + // Use *timestamp here to compensate for callback jitter etc. + } + } + @endcode + */ + const uint64_t* getHostTimeNs() const { return hasHostTime ? &hostTime : nullptr; } + //============================================================================== /** This is called by the processor to specify its details before being played. Use this version of the function if you are not interested in any sidechain and/or aux buses @@ -1473,6 +1518,9 @@ private: AudioProcessorParameterGroup parameterTree; Array flatParameterList; + uint64_t hostTime = 0; + bool hasHostTime = false; + AudioProcessorParameter* getParamChecked (int) const; #if JUCE_DEBUG diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index cf25693700..fd013c4d9b 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -37,10 +37,11 @@ struct GraphRenderSequence FloatType** audioBuffers; MidiBuffer* midiBuffers; AudioPlayHead* audioPlayHead; + Optional hostTimeNs; int numSamples; }; - void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) + void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead, Optional hostTimeNs) { auto numSamples = buffer.getNumSamples(); auto maxSamples = renderingBuffer.getNumSamples(); @@ -57,7 +58,9 @@ struct GraphRenderSequence midiChunk.clear(); midiChunk.addEvents (midiMessages, chunkStartSample, chunkSize, -chunkStartSample); - perform (audioChunk, midiChunk, audioPlayHead); + // Splitting up the buffer like this will cause the play head and host time to be + // invalid for all but the first chunk... + perform (audioChunk, midiChunk, audioPlayHead, hostTimeNs); chunkStartSample += maxSamples; } @@ -72,7 +75,7 @@ struct GraphRenderSequence currentMidiOutputBuffer.clear(); { - const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, numSamples }; + const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, hostTimeNs, numSamples }; for (auto* op : renderOps) op->perform (context); @@ -257,6 +260,7 @@ private: void perform (const Context& c) override { processor.setPlayHead (c.audioPlayHead); + processor.setHostTimeNanos (c.hostTimeNs.hasValue() ? &(*c.hostTimeNs) : nullptr); for (int i = 0; i < totalChans; ++i) audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; @@ -278,6 +282,8 @@ private: buffer.clear(); else callProcess (buffer, c.midiBuffers[midiBufferToUse]); + + processor.setHostTimeNanos (nullptr); } void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) @@ -1389,6 +1395,14 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m std::unique_ptr& renderSequence, std::atomic& isPrepared) { + const auto getHostTime = [&]() -> Optional + { + if (auto* nanos = graph.getHostTimeNs()) + return *nanos; + + return nullopt; + }; + if (graph.isNonRealtime()) { while (! isPrepared) @@ -1397,7 +1411,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m const ScopedLock sl (graph.getCallbackLock()); if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); } else { @@ -1406,7 +1420,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m if (isPrepared) { if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); } else { diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp index ddcb67576f..940173b7e3 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp @@ -226,11 +226,12 @@ void AudioProcessorPlayer::setMidiOutput (MidiOutput* midiOutputToUse) } //============================================================================== -void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData, - const int numInputChannels, - float** const outputChannelData, - const int numOutputChannels, - const int numSamples) +void AudioProcessorPlayer::audioDeviceIOCallbackWithContext (const float** const inputChannelData, + const int numInputChannels, + float** const outputChannelData, + const int numOutputChannels, + const int numSamples, + const AudioIODeviceCallbackContext& context) { const ScopedLock sl (lock); @@ -259,6 +260,16 @@ void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChann const ScopedLock sl2 (processor->getCallbackLock()); + processor->setHostTimeNanos (context.hostTimeNs); + + struct AtEndOfScope + { + ~AtEndOfScope() { proc.setHostTimeNanos (nullptr); } + AudioProcessor& proc; + }; + + const AtEndOfScope scope { *processor }; + if (! processor->isSuspended()) { if (processor->isUsingDoublePrecision()) diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h index aaf5d5012c..3e2372e2ca 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h @@ -85,7 +85,7 @@ public: //============================================================================== /** @internal */ - void audioDeviceIOCallback (const float**, int, float**, int, int) override; + void audioDeviceIOCallbackWithContext (const float**, int, float**, int, int, const AudioIODeviceCallbackContext&) override; /** @internal */ void audioDeviceAboutToStart (AudioIODevice*) override; /** @internal */