/*! @file AudioUnitSDK/AUScopeElement.cpp @copyright © 2000-2021 Apple Inc. All rights reserved. */ #include #include #include #include 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(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(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(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(&value)); // NOLINT appendBytes(&entry, sizeof(entry)); ++paramsWritten; }; constexpr UInt32 placeholderCount = 0; appendBytes(&placeholderCount, sizeof(placeholderCount)); if (mUseIndexedParameters) { const auto nparams = static_cast(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(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(p)); // NOLINT p += sizeof(UInt32); // NOLINT FloatInt32 temp{}; // NOLINT temp.i = CFSwapInt32BigToHost(*reinterpret_cast(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 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(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::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::from_create(CFStringCreateWithFormat( nullptr, nullptr, CFSTR("%u"), static_cast(i))); CFDictionarySetValue(*elementDict, *key, *el->GetName()); } } const auto key = Owned::from_create( CFStringCreateWithFormat(nullptr, nullptr, CFSTR("%u"), static_cast(mScope))); CFDictionarySetValue(inNameDict, *key, *elementDict); } } //_____________________________________________________________________________ // std::vector AUScope::RestoreElementNames(CFDictionaryRef inNameDict) const { // first we have to see if we have enough elements std::vector restoredElements; const auto maxElNum = GetNumberOfElements(); const auto dictSize = static_cast(std::max(CFDictionaryGetCount(inNameDict), CFIndex(0))); std::vector keys(dictSize); CFDictionaryGetKeysAndValues( inNameDict, reinterpret_cast(keys.data()), nullptr); // NOLINT for (size_t i = 0; i < dictSize; i++) { unsigned int intKey = 0; std::array 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(intKey) < maxElNum)) { auto* const elName = static_cast(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(&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(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(p)); // NOLINT p += sizeof(UInt32); // NOLINT p += nparams * sizeof(entry); // NOLINT } else { p = element->RestoreState(p); } return p; } } // namespace ausdk