@@ -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 */ | |||