The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
15KB

  1. /*!
  2. @file AudioUnitSDK/AUScopeElement.cpp
  3. @copyright © 2000-2021 Apple Inc. All rights reserved.
  4. */
  5. #include <AudioUnitSDK/AUBase.h>
  6. #include <AudioUnitSDK/AUScopeElement.h>
  7. #include <AudioToolbox/AudioUnitProperties.h>
  8. #include <array>
  9. namespace ausdk {
  10. //_____________________________________________________________________________
  11. //
  12. // By default, parameterIDs may be arbitrarily spaced, and a flat map
  13. // will be used for access. Calling UseIndexedParameters() will
  14. // instead use an STL vector for faster indexed access.
  15. // This assumes the paramIDs are numbered 0.....inNumberOfParameters-1
  16. // Call this before defining/adding any parameters with SetParameter()
  17. //
  18. void AUElement::UseIndexedParameters(UInt32 inNumberOfParameters)
  19. {
  20. mIndexedParameters.resize(inNumberOfParameters);
  21. mUseIndexedParameters = true;
  22. }
  23. //_____________________________________________________________________________
  24. //
  25. // Helper method.
  26. // returns whether the specified paramID is known to the element
  27. //
  28. bool AUElement::HasParameterID(AudioUnitParameterID paramID) const
  29. {
  30. if (mUseIndexedParameters) {
  31. return paramID < mIndexedParameters.size();
  32. }
  33. return mParameters.find(paramID) != mParameters.end();
  34. }
  35. //_____________________________________________________________________________
  36. //
  37. // caller assumes that this is actually an immediate parameter
  38. //
  39. AudioUnitParameterValue AUElement::GetParameter(AudioUnitParameterID paramID) const
  40. {
  41. if (mUseIndexedParameters) {
  42. ausdk::ThrowExceptionIf(
  43. paramID >= mIndexedParameters.size(), kAudioUnitErr_InvalidParameter);
  44. return mIndexedParameters[paramID].load(std::memory_order_acquire);
  45. }
  46. const auto i = mParameters.find(paramID);
  47. ausdk::ThrowExceptionIf(i == mParameters.end(), kAudioUnitErr_InvalidParameter);
  48. return (*i).second.load(std::memory_order_acquire);
  49. }
  50. //_____________________________________________________________________________
  51. //
  52. void AUElement::SetParameter(
  53. AudioUnitParameterID paramID, AudioUnitParameterValue inValue, bool okWhenInitialized)
  54. {
  55. if (mUseIndexedParameters) {
  56. ausdk::ThrowExceptionIf(
  57. paramID >= mIndexedParameters.size(), kAudioUnitErr_InvalidParameter);
  58. mIndexedParameters[paramID].store(inValue, std::memory_order_release);
  59. } else {
  60. const auto i = mParameters.find(paramID);
  61. if (i == mParameters.end()) {
  62. if (mAudioUnit.IsInitialized() && !okWhenInitialized) {
  63. // The AU should not be creating new parameters once initialized.
  64. // If a client tries to set an undefined parameter, we could throw as follows,
  65. // but this might cause a regression. So it is better to just fail silently.
  66. // Throw(kAudioUnitErr_InvalidParameter);
  67. AUSDK_LogError(
  68. "Warning: %s SetParameter for undefined param ID %u while initialized. "
  69. "Ignoring.",
  70. mAudioUnit.GetLoggingString(), static_cast<unsigned>(paramID));
  71. } else {
  72. // create new entry in map for the paramID (only happens first time)
  73. mParameters[paramID] = ParameterValue{ inValue };
  74. }
  75. } else {
  76. // paramID already exists in map so simply change its value
  77. (*i).second.store(inValue, std::memory_order_release);
  78. }
  79. }
  80. }
  81. //_____________________________________________________________________________
  82. //
  83. void AUElement::SetScheduledEvent(AudioUnitParameterID paramID,
  84. const AudioUnitParameterEvent& inEvent, UInt32 /*inSliceOffsetInBuffer*/,
  85. UInt32 /*inSliceDurationFrames*/, bool okWhenInitialized)
  86. {
  87. if (inEvent.eventType != kParameterEvent_Immediate) {
  88. AUSDK_LogError("Warning: %s was passed a ramped parameter event but does not implement "
  89. "them. Ignoring.",
  90. mAudioUnit.GetLoggingString());
  91. return;
  92. }
  93. SetParameter(paramID, inEvent.eventValues.immediate.value, okWhenInitialized); // NOLINT
  94. }
  95. //_____________________________________________________________________________
  96. //
  97. void AUElement::GetParameterList(AudioUnitParameterID* outList)
  98. {
  99. if (mUseIndexedParameters) {
  100. const auto nparams = static_cast<UInt32>(mIndexedParameters.size());
  101. for (UInt32 i = 0; i < nparams; i++) {
  102. *outList++ = (AudioUnitParameterID)i; // NOLINT
  103. }
  104. } else {
  105. for (const auto& param : mParameters) {
  106. *outList++ = param.first; // NOLINT
  107. }
  108. }
  109. }
  110. //_____________________________________________________________________________
  111. //
  112. void AUElement::SaveState(AudioUnitScope scope, CFMutableDataRef data)
  113. {
  114. AudioUnitParameterInfo paramInfo{};
  115. const CFIndex countOffset = CFDataGetLength(data);
  116. uint32_t paramsWritten = 0;
  117. const auto appendBytes = [data](const void* bytes, CFIndex length) {
  118. CFDataAppendBytes(data, static_cast<const UInt8*>(bytes), length);
  119. };
  120. const auto appendParameter = [&](AudioUnitParameterID paramID, AudioUnitParameterValue value) {
  121. struct {
  122. UInt32 paramID;
  123. UInt32 value; // really a big-endian float
  124. } entry{};
  125. static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
  126. if (mAudioUnit.GetParameterInfo(scope, paramID, paramInfo) == noErr) {
  127. if ((paramInfo.flags & kAudioUnitParameterFlag_CFNameRelease) != 0u) {
  128. if (paramInfo.cfNameString != nullptr) {
  129. CFRelease(paramInfo.cfNameString);
  130. }
  131. if (paramInfo.unit == kAudioUnitParameterUnit_CustomUnit &&
  132. paramInfo.unitName != nullptr) {
  133. CFRelease(paramInfo.unitName);
  134. }
  135. }
  136. if (((paramInfo.flags & kAudioUnitParameterFlag_OmitFromPresets) != 0u) ||
  137. ((paramInfo.flags & kAudioUnitParameterFlag_MeterReadOnly) != 0u)) {
  138. return;
  139. }
  140. }
  141. entry.paramID = CFSwapInt32HostToBig(paramID);
  142. entry.value = CFSwapInt32HostToBig(*reinterpret_cast<UInt32*>(&value)); // NOLINT
  143. appendBytes(&entry, sizeof(entry));
  144. ++paramsWritten;
  145. };
  146. constexpr UInt32 placeholderCount = 0;
  147. appendBytes(&placeholderCount, sizeof(placeholderCount));
  148. if (mUseIndexedParameters) {
  149. const auto nparams = static_cast<UInt32>(mIndexedParameters.size());
  150. for (UInt32 i = 0; i < nparams; i++) {
  151. appendParameter(i, mIndexedParameters[i]);
  152. }
  153. } else {
  154. for (const auto& item : mParameters) {
  155. appendParameter(item.first, item.second);
  156. }
  157. }
  158. const auto count_BE = CFSwapInt32HostToBig(paramsWritten);
  159. memcpy(CFDataGetMutableBytePtr(data) + countOffset, // NOLINT ptr math
  160. &count_BE, sizeof(count_BE));
  161. }
  162. //_____________________________________________________________________________
  163. //
  164. const UInt8* AUElement::RestoreState(const UInt8* state)
  165. {
  166. union FloatInt32 {
  167. UInt32 i;
  168. AudioUnitParameterValue f;
  169. };
  170. const UInt8* p = state;
  171. const UInt32 nparams = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
  172. p += sizeof(UInt32); // NOLINT
  173. for (UInt32 i = 0; i < nparams; ++i) {
  174. struct {
  175. AudioUnitParameterID paramID;
  176. AudioUnitParameterValue value;
  177. } entry{};
  178. static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
  179. entry.paramID = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
  180. p += sizeof(UInt32); // NOLINT
  181. FloatInt32 temp{}; // NOLINT
  182. temp.i = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
  183. entry.value = temp.f; // NOLINT
  184. p += sizeof(AudioUnitParameterValue); // NOLINT
  185. SetParameter(entry.paramID, entry.value);
  186. }
  187. return p;
  188. }
  189. //_____________________________________________________________________________
  190. //
  191. AUIOElement::AUIOElement(AUBase& audioUnit) : AUElement(audioUnit), mWillAllocate(true)
  192. {
  193. mStreamFormat = AudioStreamBasicDescription{ .mSampleRate = AUBase::kAUDefaultSampleRate,
  194. .mFormatID = kAudioFormatLinearPCM,
  195. .mFormatFlags = AudioFormatFlags(kAudioFormatFlagsNativeFloatPacked) |
  196. AudioFormatFlags(kAudioFormatFlagIsNonInterleaved), // NOLINT
  197. .mBytesPerPacket = sizeof(float),
  198. .mFramesPerPacket = 1,
  199. .mBytesPerFrame = sizeof(float),
  200. .mChannelsPerFrame = 2,
  201. .mBitsPerChannel = 32, // NOLINT
  202. .mReserved = 0 };
  203. }
  204. //_____________________________________________________________________________
  205. //
  206. OSStatus AUIOElement::SetStreamFormat(const AudioStreamBasicDescription& format)
  207. {
  208. mStreamFormat = format;
  209. // Clear the previous channel layout if it is inconsistent with the newly set format;
  210. // preserve it if it is acceptable, in case the new format has no layout.
  211. if (ChannelLayout().IsValid() && NumberChannels() != ChannelLayout().NumberChannels()) {
  212. RemoveAudioChannelLayout();
  213. }
  214. return noErr;
  215. }
  216. //_____________________________________________________________________________
  217. // inFramesToAllocate == 0 implies the AudioUnit's max-frames-per-slice will be used
  218. void AUIOElement::AllocateBuffer(UInt32 inFramesToAllocate)
  219. {
  220. if (GetAudioUnit().HasBegunInitializing()) {
  221. UInt32 framesToAllocate =
  222. inFramesToAllocate > 0 ? inFramesToAllocate : GetAudioUnit().GetMaxFramesPerSlice();
  223. mIOBuffer.Allocate(
  224. mStreamFormat, (mWillAllocate && NeedsBufferSpace()) ? framesToAllocate : 0);
  225. }
  226. }
  227. //_____________________________________________________________________________
  228. //
  229. void AUIOElement::DeallocateBuffer() { mIOBuffer.Deallocate(); }
  230. //_____________________________________________________________________________
  231. //
  232. // AudioChannelLayout support
  233. // return an empty vector (ie. NO channel layouts) if the AU doesn't require channel layout
  234. // knowledge
  235. std::vector<AudioChannelLayoutTag> AUIOElement::GetChannelLayoutTags() { return {}; }
  236. // outLayoutPtr WILL be NULL if called to determine layout size
  237. UInt32 AUIOElement::GetAudioChannelLayout(AudioChannelLayout* outLayoutPtr, bool& outWritable)
  238. {
  239. outWritable = true;
  240. UInt32 size = mChannelLayout.IsValid() ? mChannelLayout.Size() : 0;
  241. if (size > 0 && outLayoutPtr != nullptr) {
  242. memcpy(outLayoutPtr, &mChannelLayout.Layout(), size);
  243. }
  244. return size;
  245. }
  246. // the incoming channel map will be at least as big as a basic AudioChannelLayout
  247. // but its contents will determine its actual size
  248. // Subclass should overide if channel map is writable
  249. OSStatus AUIOElement::SetAudioChannelLayout(const AudioChannelLayout& inLayout)
  250. {
  251. if (NumberChannels() != AUChannelLayout::NumberChannels(inLayout)) {
  252. return kAudioUnitErr_InvalidPropertyValue;
  253. }
  254. mChannelLayout = inLayout;
  255. return noErr;
  256. }
  257. // Some units support optional usage of channel maps - typically converter units
  258. // that can do channel remapping between different maps. In that optional case
  259. // the user should be able to remove a channel map if that is possible.
  260. // Typically this is NOT the case (e.g., the 3DMixer even in the stereo case
  261. // needs to know if it is rendering to speakers or headphones)
  262. OSStatus AUIOElement::RemoveAudioChannelLayout()
  263. {
  264. mChannelLayout = {};
  265. return noErr;
  266. }
  267. //_____________________________________________________________________________
  268. //
  269. void AUScope::SetNumberOfElements(UInt32 numElements)
  270. {
  271. if (mDelegate != nullptr) {
  272. return mDelegate->SetNumberOfElements(numElements);
  273. }
  274. if (numElements > mElements.size()) {
  275. mElements.reserve(numElements);
  276. while (numElements > mElements.size()) {
  277. auto elem = mCreator->CreateElement(GetScope(), static_cast<UInt32>(mElements.size()));
  278. mElements.push_back(std::move(elem));
  279. }
  280. } else {
  281. while (numElements < mElements.size()) {
  282. mElements.pop_back();
  283. }
  284. }
  285. }
  286. //_____________________________________________________________________________
  287. //
  288. bool AUScope::HasElementWithName() const
  289. {
  290. for (UInt32 i = 0; i < GetNumberOfElements(); ++i) {
  291. AUElement* const el = GetElement(i);
  292. if ((el != nullptr) && el->HasName()) {
  293. return true;
  294. }
  295. }
  296. return false;
  297. }
  298. //_____________________________________________________________________________
  299. //
  300. void AUScope::AddElementNamesToDict(CFMutableDictionaryRef inNameDict) const
  301. {
  302. if (HasElementWithName()) {
  303. const auto elementDict =
  304. Owned<CFMutableDictionaryRef>::from_create(CFDictionaryCreateMutable(
  305. nullptr, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
  306. for (UInt32 i = 0; i < GetNumberOfElements(); ++i) {
  307. AUElement* const el = GetElement(i);
  308. if (el != nullptr && el->HasName()) {
  309. const auto key = Owned<CFStringRef>::from_create(CFStringCreateWithFormat(
  310. nullptr, nullptr, CFSTR("%u"), static_cast<unsigned>(i)));
  311. CFDictionarySetValue(*elementDict, *key, *el->GetName());
  312. }
  313. }
  314. const auto key = Owned<CFStringRef>::from_create(
  315. CFStringCreateWithFormat(nullptr, nullptr, CFSTR("%u"), static_cast<unsigned>(mScope)));
  316. CFDictionarySetValue(inNameDict, *key, *elementDict);
  317. }
  318. }
  319. //_____________________________________________________________________________
  320. //
  321. std::vector<AudioUnitElement> AUScope::RestoreElementNames(CFDictionaryRef inNameDict) const
  322. {
  323. // first we have to see if we have enough elements
  324. std::vector<AudioUnitElement> restoredElements;
  325. const auto maxElNum = GetNumberOfElements();
  326. const auto dictSize =
  327. static_cast<size_t>(std::max(CFDictionaryGetCount(inNameDict), CFIndex(0)));
  328. std::vector<CFStringRef> keys(dictSize);
  329. CFDictionaryGetKeysAndValues(
  330. inNameDict, reinterpret_cast<const void**>(keys.data()), nullptr); // NOLINT
  331. for (size_t i = 0; i < dictSize; i++) {
  332. unsigned int intKey = 0;
  333. std::array<char, 32> string{};
  334. CFStringGetCString(keys[i], string.data(), string.size(), kCFStringEncodingASCII);
  335. const int result = sscanf(string.data(), "%u", &intKey); // NOLINT
  336. // check if sscanf succeeded and element index is less than max elements.
  337. if ((result != 0) && (static_cast<UInt32>(intKey) < maxElNum)) {
  338. auto* const elName =
  339. static_cast<CFStringRef>(CFDictionaryGetValue(inNameDict, keys[i]));
  340. if ((elName != nullptr) && (CFGetTypeID(elName) == CFStringGetTypeID())) {
  341. AUElement* const element = GetElement(intKey);
  342. if (element != nullptr) {
  343. auto* const currentName = element->GetName().get();
  344. if (currentName == nullptr || CFStringCompare(elName, currentName, 0) != kCFCompareEqualTo) {
  345. element->SetName(elName);
  346. restoredElements.push_back(intKey);
  347. }
  348. }
  349. }
  350. }
  351. }
  352. return restoredElements;
  353. }
  354. void AUScope::SaveState(CFMutableDataRef data) const
  355. {
  356. const AudioUnitElement nElems = GetNumberOfElements();
  357. for (AudioUnitElement ielem = 0; ielem < nElems; ++ielem) {
  358. AUElement* const element = GetElement(ielem);
  359. const UInt32 nparams = element->GetNumberOfParameters();
  360. if (nparams > 0) {
  361. struct {
  362. const UInt32 scope;
  363. const UInt32 element;
  364. } hdr{ .scope = CFSwapInt32HostToBig(GetScope()),
  365. .element = CFSwapInt32HostToBig(ielem) };
  366. static_assert(sizeof(hdr) == (sizeof(hdr.scope) + sizeof(hdr.element)));
  367. CFDataAppendBytes(data, reinterpret_cast<const UInt8*>(&hdr), sizeof(hdr)); // NOLINT
  368. element->SaveState(mScope, data);
  369. }
  370. }
  371. }
  372. const UInt8* AUScope::RestoreState(const UInt8* state) const
  373. {
  374. const UInt8* p = state;
  375. const UInt32 elementIdx = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
  376. p += sizeof(UInt32); // NOLINT
  377. AUElement* const element = GetElement(elementIdx);
  378. if (element == nullptr) {
  379. struct {
  380. AudioUnitParameterID paramID;
  381. AudioUnitParameterValue value;
  382. } entry{};
  383. static_assert(sizeof(entry) == (sizeof(entry.paramID) + sizeof(entry.value)));
  384. const UInt32 nparams = CFSwapInt32BigToHost(*reinterpret_cast<const UInt32*>(p)); // NOLINT
  385. p += sizeof(UInt32); // NOLINT
  386. p += nparams * sizeof(entry); // NOLINT
  387. } else {
  388. p = element->RestoreState(p);
  389. }
  390. return p;
  391. }
  392. } // namespace ausdk