1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00
JUCE/modules/juce_audio_plugin_client/AU/AudioUnitSDK/AUScopeElement.cpp

446 lines
15 KiB
C++

/*!
@file AudioUnitSDK/AUScopeElement.cpp
@copyright © 2000-2021 Apple Inc. All rights reserved.
*/
#include <AudioUnitSDK/AUBase.h>
#include <AudioUnitSDK/AUScopeElement.h>
#include <AudioToolbox/AudioUnitProperties.h>
#include <array>
namespace ausdk {
//_____________________________________________________________________________
//
// By default, parameterIDs may be arbitrarily spaced, and a flat map
// will be used for access. Calling UseIndexedParameters() will
// instead use an STL vector for faster indexed access.
// This assumes the paramIDs are numbered 0.....inNumberOfParameters-1
// Call this before defining/adding any parameters with SetParameter()
//
void AUElement::UseIndexedParameters(UInt32 inNumberOfParameters)
{
mIndexedParameters.resize(inNumberOfParameters);
mUseIndexedParameters = true;
}
//_____________________________________________________________________________
//
// Helper method.
// returns whether the specified paramID is known to the element
//
bool AUElement::HasParameterID(AudioUnitParameterID paramID) const
{
if (mUseIndexedParameters) {
return paramID < mIndexedParameters.size();
}
return mParameters.find(paramID) != mParameters.end();
}
//_____________________________________________________________________________
//
// caller assumes that this is actually an immediate parameter
//
AudioUnitParameterValue AUElement::GetParameter(AudioUnitParameterID paramID) const
{
if (mUseIndexedParameters) {
ausdk::ThrowExceptionIf(
paramID >= mIndexedParameters.size(), kAudioUnitErr_InvalidParameter);
return mIndexedParameters[paramID].load(std::memory_order_acquire);
}
const auto i = mParameters.find(paramID);
ausdk::ThrowExceptionIf(i == mParameters.end(), kAudioUnitErr_InvalidParameter);
return (*i).second.load(std::memory_order_acquire);
}
//_____________________________________________________________________________
//
void AUElement::SetParameter(
AudioUnitParameterID paramID, AudioUnitParameterValue inValue, bool okWhenInitialized)
{
if (mUseIndexedParameters) {
ausdk::ThrowExceptionIf(
paramID >= mIndexedParameters.size(), kAudioUnitErr_InvalidParameter);
mIndexedParameters[paramID].store(inValue, std::memory_order_release);
} else {
const auto i = mParameters.find(paramID);
if (i == mParameters.end()) {
if (mAudioUnit.IsInitialized() && !okWhenInitialized) {
// The AU should not be creating new parameters once initialized.
// If a client tries to set an undefined parameter, we could throw as follows,
// but this might cause a regression. So it is better to just fail silently.
// Throw(kAudioUnitErr_InvalidParameter);
AUSDK_LogError(
"Warning: %s SetParameter for undefined param ID %u while initialized. "
"Ignoring.",
mAudioUnit.GetLoggingString(), static_cast<unsigned>(paramID));
} else {
// create new entry in map for the paramID (only happens first time)
mParameters[paramID] = ParameterValue{ inValue };
}
} else {
// paramID already exists in map so simply change its value
(*i).second.store(inValue, std::memory_order_release);
}
}
}
//_____________________________________________________________________________
//
void AUElement::SetScheduledEvent(AudioUnitParameterID paramID,
const AudioUnitParameterEvent& inEvent, UInt32 /*inSliceOffsetInBuffer*/,
UInt32 /*inSliceDurationFrames*/, bool okWhenInitialized)
{
if (inEvent.eventType != kParameterEvent_Immediate) {
AUSDK_LogError("Warning: %s was passed a ramped parameter event but does not implement "
"them. Ignoring.",
mAudioUnit.GetLoggingString());
return;
}
SetParameter(paramID, inEvent.eventValues.immediate.value, okWhenInitialized); // NOLINT
}
//_____________________________________________________________________________
//
void AUElement::GetParameterList(AudioUnitParameterID* outList)
{
if (mUseIndexedParameters) {
const auto nparams = static_cast<UInt32>(mIndexedParameters.size());
for (UInt32 i = 0; i < nparams; i++) {
*outList++ = (AudioUnitParameterID)i; // NOLINT
}
} else {
for (const auto& param : mParameters) {
*outList++ = param.first; // NOLINT
}
}
}
//_____________________________________________________________________________
//
void AUElement::SaveState(AudioUnitScope scope, CFMutableDataRef data)
{
AudioUnitParameterInfo paramInfo{};
const CFIndex countOffset = CFDataGetLength(data);
uint32_t paramsWritten = 0;
const auto appendBytes = [data](const void* bytes, CFIndex length) {
CFDataAppendBytes(data, static_cast<const UInt8*>(bytes), length);
};
const auto appendParameter = [&](AudioUnitParameterID paramID, AudioUnitParameterValue value) {
struct {
UInt32 paramID;
UInt32 value; // really a big-endian float
} entry{};
static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
if (mAudioUnit.GetParameterInfo(scope, paramID, paramInfo) == noErr) {
if ((paramInfo.flags & kAudioUnitParameterFlag_CFNameRelease) != 0u) {
if (paramInfo.cfNameString != nullptr) {
CFRelease(paramInfo.cfNameString);
}
if (paramInfo.unit == kAudioUnitParameterUnit_CustomUnit &&
paramInfo.unitName != nullptr) {
CFRelease(paramInfo.unitName);
}
}
if (((paramInfo.flags & kAudioUnitParameterFlag_OmitFromPresets) != 0u) ||
((paramInfo.flags & kAudioUnitParameterFlag_MeterReadOnly) != 0u)) {
return;
}
}
entry.paramID = CFSwapInt32HostToBig(paramID);
entry.value = CFSwapInt32HostToBig(*reinterpret_cast<UInt32*>(&value)); // NOLINT
appendBytes(&entry, sizeof(entry));
++paramsWritten;
};
constexpr UInt32 placeholderCount = 0;
appendBytes(&placeholderCount, sizeof(placeholderCount));
if (mUseIndexedParameters) {
const auto nparams = static_cast<UInt32>(mIndexedParameters.size());
for (UInt32 i = 0; i < nparams; i++) {
appendParameter(i, mIndexedParameters[i]);
}
} else {
for (const auto& item : mParameters) {
appendParameter(item.first, item.second);
}
}
const auto count_BE = CFSwapInt32HostToBig(paramsWritten);
memcpy(CFDataGetMutableBytePtr(data) + countOffset, // NOLINT ptr math
&count_BE, sizeof(count_BE));
}
//_____________________________________________________________________________
//
const UInt8* AUElement::RestoreState(const UInt8* state)
{
union FloatInt32 {
UInt32 i;
AudioUnitParameterValue f;
};
const UInt8* p = state;
const UInt32 nparams = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
p += sizeof(UInt32); // NOLINT
for (UInt32 i = 0; i < nparams; ++i) {
struct {
AudioUnitParameterID paramID;
AudioUnitParameterValue value;
} entry{};
static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
entry.paramID = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
p += sizeof(UInt32); // NOLINT
FloatInt32 temp{}; // NOLINT
temp.i = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
entry.value = temp.f; // NOLINT
p += sizeof(AudioUnitParameterValue); // NOLINT
SetParameter(entry.paramID, entry.value);
}
return p;
}
//_____________________________________________________________________________
//
AUIOElement::AUIOElement(AUBase& audioUnit) : AUElement(audioUnit), mWillAllocate(true)
{
mStreamFormat = AudioStreamBasicDescription{ .mSampleRate = AUBase::kAUDefaultSampleRate,
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = AudioFormatFlags(kAudioFormatFlagsNativeFloatPacked) |
AudioFormatFlags(kAudioFormatFlagIsNonInterleaved), // NOLINT
.mBytesPerPacket = sizeof(float),
.mFramesPerPacket = 1,
.mBytesPerFrame = sizeof(float),
.mChannelsPerFrame = 2,
.mBitsPerChannel = 32, // NOLINT
.mReserved = 0 };
}
//_____________________________________________________________________________
//
OSStatus AUIOElement::SetStreamFormat(const AudioStreamBasicDescription& format)
{
mStreamFormat = format;
// Clear the previous channel layout if it is inconsistent with the newly set format;
// preserve it if it is acceptable, in case the new format has no layout.
if (ChannelLayout().IsValid() && NumberChannels() != ChannelLayout().NumberChannels()) {
RemoveAudioChannelLayout();
}
return noErr;
}
//_____________________________________________________________________________
// inFramesToAllocate == 0 implies the AudioUnit's max-frames-per-slice will be used
void AUIOElement::AllocateBuffer(UInt32 inFramesToAllocate)
{
if (GetAudioUnit().HasBegunInitializing()) {
UInt32 framesToAllocate =
inFramesToAllocate > 0 ? inFramesToAllocate : GetAudioUnit().GetMaxFramesPerSlice();
mIOBuffer.Allocate(
mStreamFormat, (mWillAllocate && NeedsBufferSpace()) ? framesToAllocate : 0);
}
}
//_____________________________________________________________________________
//
void AUIOElement::DeallocateBuffer() { mIOBuffer.Deallocate(); }
//_____________________________________________________________________________
//
// AudioChannelLayout support
// return an empty vector (ie. NO channel layouts) if the AU doesn't require channel layout
// knowledge
std::vector<AudioChannelLayoutTag> AUIOElement::GetChannelLayoutTags() { return {}; }
// outLayoutPtr WILL be NULL if called to determine layout size
UInt32 AUIOElement::GetAudioChannelLayout(AudioChannelLayout* outLayoutPtr, bool& outWritable)
{
outWritable = true;
UInt32 size = mChannelLayout.IsValid() ? mChannelLayout.Size() : 0;
if (size > 0 && outLayoutPtr != nullptr) {
memcpy(outLayoutPtr, &mChannelLayout.Layout(), size);
}
return size;
}
// the incoming channel map will be at least as big as a basic AudioChannelLayout
// but its contents will determine its actual size
// Subclass should overide if channel map is writable
OSStatus AUIOElement::SetAudioChannelLayout(const AudioChannelLayout& inLayout)
{
if (NumberChannels() != AUChannelLayout::NumberChannels(inLayout)) {
return kAudioUnitErr_InvalidPropertyValue;
}
mChannelLayout = inLayout;
return noErr;
}
// Some units support optional usage of channel maps - typically converter units
// that can do channel remapping between different maps. In that optional case
// the user should be able to remove a channel map if that is possible.
// Typically this is NOT the case (e.g., the 3DMixer even in the stereo case
// needs to know if it is rendering to speakers or headphones)
OSStatus AUIOElement::RemoveAudioChannelLayout()
{
mChannelLayout = {};
return noErr;
}
//_____________________________________________________________________________
//
void AUScope::SetNumberOfElements(UInt32 numElements)
{
if (mDelegate != nullptr) {
return mDelegate->SetNumberOfElements(numElements);
}
if (numElements > mElements.size()) {
mElements.reserve(numElements);
while (numElements > mElements.size()) {
auto elem = mCreator->CreateElement(GetScope(), static_cast<UInt32>(mElements.size()));
mElements.push_back(std::move(elem));
}
} else {
while (numElements < mElements.size()) {
mElements.pop_back();
}
}
}
//_____________________________________________________________________________
//
bool AUScope::HasElementWithName() const
{
for (UInt32 i = 0; i < GetNumberOfElements(); ++i) {
AUElement* const el = GetElement(i);
if ((el != nullptr) && el->HasName()) {
return true;
}
}
return false;
}
//_____________________________________________________________________________
//
void AUScope::AddElementNamesToDict(CFMutableDictionaryRef inNameDict) const
{
if (HasElementWithName()) {
const auto elementDict =
Owned<CFMutableDictionaryRef>::from_create(CFDictionaryCreateMutable(
nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
for (UInt32 i = 0; i < GetNumberOfElements(); ++i) {
AUElement* const el = GetElement(i);
if (el != nullptr && el->HasName()) {
const auto key = Owned<CFStringRef>::from_create(CFStringCreateWithFormat(
nullptr, nullptr, CFSTR("%u"), static_cast<unsigned>(i)));
CFDictionarySetValue(*elementDict, *key, *el->GetName());
}
}
const auto key = Owned<CFStringRef>::from_create(
CFStringCreateWithFormat(nullptr, nullptr, CFSTR("%u"), static_cast<unsigned>(mScope)));
CFDictionarySetValue(inNameDict, *key, *elementDict);
}
}
//_____________________________________________________________________________
//
std::vector<AudioUnitElement> AUScope::RestoreElementNames(CFDictionaryRef inNameDict) const
{
// first we have to see if we have enough elements
std::vector<AudioUnitElement> restoredElements;
const auto maxElNum = GetNumberOfElements();
const auto dictSize =
static_cast<size_t>(std::max(CFDictionaryGetCount(inNameDict), CFIndex(0)));
std::vector<CFStringRef> keys(dictSize);
CFDictionaryGetKeysAndValues(
inNameDict, reinterpret_cast<const void**>(keys.data()), nullptr); // NOLINT
for (size_t i = 0; i < dictSize; i++) {
unsigned int intKey = 0;
std::array<char, 32> string{};
CFStringGetCString(keys[i], string.data(), string.size(), kCFStringEncodingASCII);
const int result = sscanf(string.data(), "%u", &intKey); // NOLINT
// check if sscanf succeeded and element index is less than max elements.
if ((result != 0) && (static_cast<UInt32>(intKey) < maxElNum)) {
auto* const elName =
static_cast<CFStringRef>(CFDictionaryGetValue(inNameDict, keys[i]));
if ((elName != nullptr) && (CFGetTypeID(elName) == CFStringGetTypeID())) {
AUElement* const element = GetElement(intKey);
if (element != nullptr) {
auto* const currentName = element->GetName().get();
if (currentName == nullptr || CFStringCompare(elName, currentName, 0) != kCFCompareEqualTo) {
element->SetName(elName);
restoredElements.push_back(intKey);
}
}
}
}
}
return restoredElements;
}
void AUScope::SaveState(CFMutableDataRef data) const
{
const AudioUnitElement nElems = GetNumberOfElements();
for (AudioUnitElement ielem = 0; ielem < nElems; ++ielem) {
AUElement* const element = GetElement(ielem);
const UInt32 nparams = element->GetNumberOfParameters();
if (nparams > 0) {
struct {
const UInt32 scope;
const UInt32 element;
} hdr{ .scope = CFSwapInt32HostToBig(GetScope()),
.element = CFSwapInt32HostToBig(ielem) };
static_assert(sizeof(hdr) == (sizeof(hdr.scope) + sizeof(hdr.element)));
CFDataAppendBytes(data, reinterpret_cast<const UInt8*>(&hdr), sizeof(hdr)); // NOLINT
element->SaveState(mScope, data);
}
}
}
const UInt8* AUScope::RestoreState(const UInt8* state) const
{
const UInt8* p = state;
const UInt32 elementIdx = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
p += sizeof(UInt32); // NOLINT
AUElement* const element = GetElement(elementIdx);
if (element == nullptr) {
struct {
AudioUnitParameterID paramID;
AudioUnitParameterValue value;
} entry{};
static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
const UInt32 nparams = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
p += sizeof(UInt32); // NOLINT
p += nparams * sizeof(entry); // NOLINT
} else {
p = element->RestoreState(p);
}
return p;
}
} // namespace ausdk