mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
AudioProcessor: Allow querying of the host timestamp in processBlock
This commit is contained in:
parent
5d096b46d7
commit
cfa289d943
31 changed files with 496 additions and 185 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue