| @@ -31,6 +31,8 @@ | |||
| #include "juce_audio_basics.h" | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| #if JUCE_MINGW && ! defined (alloca) | |||
| #define alloca __builtin_alloca | |||
| #endif | |||
| @@ -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" | |||
| @@ -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 <mach/mach_time.h> | |||
| 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 | |||
| @@ -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) | |||
| { | |||
| @@ -526,8 +526,12 @@ private: | |||
| class CallbackHandler; | |||
| std::unique_ptr<CallbackHandler> 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&); | |||
| @@ -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. | |||
| @@ -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 | |||
| { | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -20,6 +20,8 @@ | |||
| ============================================================================== | |||
| */ | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h> | |||
| 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<float> audioData { 0, 0 }; | |||
| }; | |||
| CoreAudioTimeConversions timeConversions; | |||
| IOChannelData channelData; | |||
| BigInteger requestedInputChannels, requestedOutputChannels; | |||
| @@ -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 | |||
| { | |||
| @@ -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, | |||
| {}); | |||
| } | |||
| } | |||
| @@ -462,8 +462,12 @@ private: | |||
| if (callback != nullptr) | |||
| { | |||
| if ((numActiveInChans + numActiveOutChans) > 0) | |||
| callback->audioDeviceIOCallback (const_cast<const float**> (inChans.getData()), numActiveInChans, | |||
| outChans, numActiveOutChans, numSamples); | |||
| callback->audioDeviceIOCallbackWithContext (const_cast<const float**> (inChans.getData()), | |||
| numActiveInChans, | |||
| outChans, | |||
| numActiveOutChans, | |||
| numSamples, | |||
| {}); | |||
| } | |||
| else | |||
| { | |||
| @@ -20,6 +20,8 @@ | |||
| ============================================================================== | |||
| */ | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h> | |||
| 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<const float**> (tempInputBuffers.get()), | |||
| numInputChans, | |||
| tempOutputBuffers, | |||
| numOutputChans, | |||
| bufferSize); | |||
| const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0; | |||
| callback->audioDeviceIOCallbackWithContext (const_cast<const float**> (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<CoreAudioInternal*> (device)->audioCallback (inInputData, outOutputData); | |||
| static_cast<CoreAudioInternal*> (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) | |||
| { | |||
| @@ -1326,8 +1326,12 @@ private: | |||
| inputFormat[i].convertToFloat (infos[i].buffers[bufferIndex], inBuffers[i], samps); | |||
| } | |||
| currentCallback->audioDeviceIOCallback (const_cast<const float**> (inBuffers.getData()), numActiveInputChans, | |||
| outBuffers, numActiveOutputChans, samps); | |||
| currentCallback->audioDeviceIOCallbackWithContext (const_cast<const float**> (inBuffers.getData()), | |||
| numActiveInputChans, | |||
| outBuffers, | |||
| numActiveOutputChans, | |||
| samps, | |||
| {}); | |||
| for (int i = 0; i < numActiveOutputChans; ++i) | |||
| { | |||
| @@ -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 | |||
| { | |||
| @@ -1515,8 +1515,12 @@ public: | |||
| const ScopedTryLock sl (startStopLock); | |||
| if (sl.isLocked() && isStarted) | |||
| callback->audioDeviceIOCallback (const_cast<const float**> (inputBuffers), numInputBuffers, | |||
| outputBuffers, numOutputBuffers, bufferSize); | |||
| callback->audioDeviceIOCallbackWithContext (const_cast<const float**> (inputBuffers), | |||
| numInputBuffers, | |||
| outputBuffers, | |||
| numOutputBuffers, | |||
| bufferSize, | |||
| {}); | |||
| else | |||
| outs.clear(); | |||
| } | |||
| @@ -77,6 +77,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| #include "../utility/juce_CarbonVisibility.h" | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioLayouts.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h> | |||
| #include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | |||
| #include <juce_audio_processors/format_types/juce_AU_Shared.h> | |||
| @@ -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<AUPreset> presetsArray; | |||
| CriticalSection incomingMidiLock; | |||
| @@ -51,6 +51,7 @@ | |||
| #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioLayouts.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h> | |||
| #include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | |||
| #include <juce_audio_processors/format_types/juce_AU_Shared.h> | |||
| @@ -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<AUAudioUnitBusArray, NSObjectDeleter> inputBusses, outputBusses; | |||
| ObjCBlock<AUImplementorValueObserver> paramObserver; | |||
| @@ -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 | |||
| @@ -18,6 +18,7 @@ | |||
| #include <juce_core/system/juce_CompilerWarnings.h> | |||
| #include <juce_core/system/juce_TargetPlatform.h> | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| #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<Vst2::VstTimeInfo*> (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<CurrentPositionInfo> currentPosition; | |||
| LegacyAudioParametersWrapper juceParameters; | |||
| @@ -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) | |||
| @@ -34,6 +34,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| #include <CoreAudioKit/AUViewController.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h> | |||
| #include <juce_audio_basics/native/juce_mac_CoreAudioLayouts.h> | |||
| #include <juce_audio_basics/midi/juce_MidiDataConcatenator.h> | |||
| #include "juce_AU_Shared.h" | |||
| @@ -332,39 +333,25 @@ namespace AudioUnitFormatHelpers | |||
| #endif | |||
| template <typename Value> | |||
| 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 <typename Value> | |||
| static BasicOptional<Value> tryGetProperty (AudioUnit inUnit, | |||
| AudioUnitPropertyID inID, | |||
| AudioUnitScope inScope, | |||
| AudioUnitElement inElement) | |||
| static Optional<Value> 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<Value> (data); | |||
| return data; | |||
| return BasicOptional<Value>(); | |||
| return {}; | |||
| } | |||
| static UInt32 getElementCount (AudioUnit comp, AudioUnitScope scope) noexcept | |||
| { | |||
| const auto count = tryGetProperty<UInt32> (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<AudioChannelLayout> (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<float>& 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<double> (numSamples) * 1.0e9 / sampleRate; | |||
| auto bufferTicks = static_cast<UInt64> (std::ceil (bufferNanos * (static_cast<double> (sTimebaseInfo.denom) | |||
| / static_cast<double> (sTimebaseInfo.numer)))); | |||
| currentTime += bufferTicks; | |||
| return currentTime; | |||
| } | |||
| bool isBusCountWritable (bool isInput) const noexcept | |||
| { | |||
| UInt32 countSize; | |||
| @@ -20,6 +20,8 @@ | |||
| #ifndef DOXYGEN | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| 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 <typename Item> | |||
| struct BasicOptional final | |||
| { | |||
| BasicOptional() noexcept = default; | |||
| BasicOptional (const Item& i) noexcept : item { i }, isValid { true } {} | |||
| Item item; | |||
| bool isValid{}; | |||
| }; | |||
| static BasicOptional<Steinberg::Vst::Event> createVstEvent (const MidiMessage& msg, | |||
| const uint8* midiEventData, | |||
| EventConversionKind kind) noexcept | |||
| static Optional<Steinberg::Vst::Event> createVstEvent (const MidiMessage& msg, | |||
| const uint8* midiEventData, | |||
| EventConversionKind kind) noexcept | |||
| { | |||
| if (msg.isNoteOn()) | |||
| return createNoteOnEvent (msg); | |||
| @@ -1290,7 +1277,7 @@ private: | |||
| return {}; | |||
| } | |||
| static BasicOptional<MidiMessage> toMidiMessage (const Steinberg::Vst::LegacyMIDICCOutEvent& e) | |||
| static Optional<MidiMessage> toMidiMessage (const Steinberg::Vst::LegacyMIDICCOutEvent& e) | |||
| { | |||
| if (e.controlNumber <= 127) | |||
| return MidiMessage::controllerEvent (createSafeChannel (int16 (e.channel)), | |||
| @@ -1327,7 +1314,7 @@ private: | |||
| } | |||
| } | |||
| static BasicOptional<MidiMessage> toMidiMessage (const Steinberg::Vst::Event& e) | |||
| static Optional<MidiMessage> toMidiMessage (const Steinberg::Vst::Event& e) | |||
| { | |||
| switch (e.type) | |||
| { | |||
| @@ -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; | |||
| } | |||
| @@ -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<Vst2::VstInt32> | |||
| { | |||
| 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) | |||
| @@ -33,6 +33,7 @@ | |||
| #include "juce_audio_processors.h" | |||
| #include <juce_gui_extra/juce_gui_extra.h> | |||
| #include <juce_core/containers/juce_Optional.h> | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| @@ -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<float>&, 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<AudioProcessorParameter*> flatParameterList; | |||
| uint64_t hostTime = 0; | |||
| bool hasHostTime = false; | |||
| AudioProcessorParameter* getParamChecked (int) const; | |||
| #if JUCE_DEBUG | |||
| @@ -37,10 +37,11 @@ struct GraphRenderSequence | |||
| FloatType** audioBuffers; | |||
| MidiBuffer* midiBuffers; | |||
| AudioPlayHead* audioPlayHead; | |||
| Optional<uint64_t> hostTimeNs; | |||
| int numSamples; | |||
| }; | |||
| void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) | |||
| void perform (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead, Optional<uint64_t> 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<float>& buffer, MidiBuffer& midiMessages) | |||
| @@ -1389,6 +1395,14 @@ static void processBlockForBuffer (AudioBuffer<FloatType>& buffer, MidiBuffer& m | |||
| std::unique_ptr<SequenceType>& renderSequence, | |||
| std::atomic<bool>& isPrepared) | |||
| { | |||
| const auto getHostTime = [&]() -> Optional<uint64_t> | |||
| { | |||
| if (auto* nanos = graph.getHostTimeNs()) | |||
| return *nanos; | |||
| return nullopt; | |||
| }; | |||
| if (graph.isNonRealtime()) | |||
| { | |||
| while (! isPrepared) | |||
| @@ -1397,7 +1411,7 @@ static void processBlockForBuffer (AudioBuffer<FloatType>& 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<FloatType>& buffer, MidiBuffer& m | |||
| if (isPrepared) | |||
| { | |||
| if (renderSequence != nullptr) | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); | |||
| renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); | |||
| } | |||
| else | |||
| { | |||
| @@ -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()) | |||
| @@ -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 */ | |||