3
0

AudioControlsLoader.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AudioControlsLoader.h>
  9. #include <AzCore/std/string/conversions.h>
  10. #include <AzCore/Utils/Utils.h>
  11. #include <ACEEnums.h>
  12. #include <ATLCommon.h>
  13. #include <ATLControlsModel.h>
  14. #include <AudioFileUtils.h>
  15. #include <IAudioSystem.h>
  16. #include <IAudioSystemControl.h>
  17. #include <IAudioSystemEditor.h>
  18. #include <QAudioControlTreeWidget.h>
  19. #include <Util/UndoUtil.h>
  20. #include <QStandardItem>
  21. namespace AudioControls
  22. {
  23. //-------------------------------------------------------------------------------------------//
  24. namespace LoaderStrings
  25. {
  26. static constexpr const char* LevelsSubFolder = "levels";
  27. } // namespace LoaderStrings
  28. //-------------------------------------------------------------------------------------------//
  29. EACEControlType TagToType(const AZStd::string_view tag)
  30. {
  31. // maybe a simple map would be better.
  32. if (tag == Audio::ATLXmlTags::ATLTriggerTag)
  33. {
  34. return eACET_TRIGGER;
  35. }
  36. else if (tag == Audio::ATLXmlTags::ATLSwitchTag)
  37. {
  38. return eACET_SWITCH;
  39. }
  40. else if (tag == Audio::ATLXmlTags::ATLSwitchStateTag)
  41. {
  42. return eACET_SWITCH_STATE;
  43. }
  44. else if (tag == Audio::ATLXmlTags::ATLRtpcTag)
  45. {
  46. return eACET_RTPC;
  47. }
  48. else if (tag == Audio::ATLXmlTags::ATLEnvironmentTag)
  49. {
  50. return eACET_ENVIRONMENT;
  51. }
  52. else if (tag == Audio::ATLXmlTags::ATLPreloadRequestTag)
  53. {
  54. return eACET_PRELOAD;
  55. }
  56. return eACET_NUM_TYPES;
  57. }
  58. //-------------------------------------------------------------------------------------------//
  59. CAudioControlsLoader::CAudioControlsLoader(CATLControlsModel* atlControlsModel, QStandardItemModel* layoutModel, IAudioSystemEditor* audioSystemImpl)
  60. : m_atlControlsModel(atlControlsModel)
  61. , m_layoutModel(layoutModel)
  62. , m_audioSystemImpl(audioSystemImpl)
  63. {}
  64. //-------------------------------------------------------------------------------------------//
  65. void CAudioControlsLoader::LoadAll()
  66. {
  67. LoadScopes();
  68. LoadControls();
  69. }
  70. //-------------------------------------------------------------------------------------------//
  71. void CAudioControlsLoader::LoadControls()
  72. {
  73. const CUndoSuspend suspendUndo;
  74. // Get the relative path (under asset root) where the controls live.
  75. const char* controlsPath = AZ::Interface<Audio::IAudioSystem>::Get()->GetControlsPath();
  76. // Get the full path up to asset root.
  77. AZ::IO::FixedMaxPath controlsFullPath = AZ::Utils::GetProjectPath();
  78. controlsFullPath /= controlsPath;
  79. // load the global controls
  80. LoadAllLibrariesInFolder(controlsFullPath.Native(), "");
  81. AZ::IO::FixedMaxPath searchPath = controlsFullPath / LoaderStrings::LevelsSubFolder;
  82. auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*");
  83. for (const auto& file : foundFiles)
  84. {
  85. if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(file.c_str()))
  86. {
  87. AZStd::string levelName{ file.Filename().Native() };
  88. LoadAllLibrariesInFolder(controlsFullPath.Native(), levelName);
  89. if (!m_atlControlsModel->ScopeExists(levelName))
  90. {
  91. // If the scope doesn't exist it means it is not a real
  92. // level in the project so it's flagged as LocalOnly
  93. m_atlControlsModel->AddScope(levelName, true);
  94. }
  95. }
  96. }
  97. CreateDefaultControls();
  98. }
  99. //-------------------------------------------------------------------------------------------//
  100. void CAudioControlsLoader::LoadAllLibrariesInFolder(const AZStd::string_view folderPath, const AZStd::string_view level)
  101. {
  102. AZ::IO::FixedMaxPath searchPath{ folderPath };
  103. if (!level.empty())
  104. {
  105. searchPath /= LoaderStrings::LevelsSubFolder;
  106. searchPath /= level;
  107. }
  108. auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*.xml");
  109. for (auto& file : foundFiles)
  110. {
  111. Audio::ScopedXmlLoader xmlLoader(file.Native());
  112. if (xmlLoader.HasError())
  113. {
  114. AZ_Warning("AudioControlsLoader", false, "Unable to load the xml file '%s'", file.c_str());
  115. continue;
  116. }
  117. auto xmlRootNode = xmlLoader.GetRootNode();
  118. if (xmlRootNode && azstricmp(xmlRootNode->name(), Audio::ATLXmlTags::RootNodeTag) == 0)
  119. {
  120. AZ::IO::PathView fileName = file.Filename();
  121. AZStd::to_lower(file.Native().begin(), file.Native().end());
  122. m_loadedFilenames.insert(file.c_str());
  123. if (auto nameAttr = xmlRootNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false); nameAttr != nullptr)
  124. {
  125. fileName = nameAttr->value();
  126. }
  127. else
  128. {
  129. fileName = fileName.Stem();
  130. }
  131. LoadControlsLibrary(xmlRootNode, folderPath, level, fileName.Native());
  132. }
  133. }
  134. }
  135. //-------------------------------------------------------------------------------------------//
  136. QStandardItem* CAudioControlsLoader::AddFolder(QStandardItem* parentItem, const QString& name)
  137. {
  138. if (parentItem && !name.isEmpty())
  139. {
  140. const int size = parentItem->rowCount();
  141. for (int i = 0; i < size; ++i)
  142. {
  143. QStandardItem* item = parentItem->child(i);
  144. if (item && (item->data(eDR_TYPE) == eIT_FOLDER) && (QString::compare(name, item->text(), Qt::CaseInsensitive) == 0))
  145. {
  146. return item;
  147. }
  148. }
  149. QStandardItem* item = new QFolderItem(name);
  150. if (parentItem && item)
  151. {
  152. parentItem->appendRow(item);
  153. return item;
  154. }
  155. }
  156. return nullptr;
  157. }
  158. //-------------------------------------------------------------------------------------------//
  159. QStandardItem* CAudioControlsLoader::AddUniqueFolderPath(QStandardItem* parentItem, const QString& path)
  160. {
  161. QStringList folderNames = path.split(QRegExp("(\\\\|\\/)"), Qt::SkipEmptyParts);
  162. const int size = folderNames.length();
  163. for (int i = 0; i < size; ++i)
  164. {
  165. if (!folderNames[i].isEmpty())
  166. {
  167. QStandardItem* childItem = AddFolder(parentItem, folderNames[i]);
  168. if (childItem)
  169. {
  170. parentItem = childItem;
  171. }
  172. }
  173. }
  174. return parentItem;
  175. }
  176. //-------------------------------------------------------------------------------------------//
  177. void CAudioControlsLoader::LoadControlsLibrary(
  178. const AZ::rapidxml::xml_node<char>* rootNode,
  179. [[maybe_unused]] const AZStd::string_view filePath,
  180. const AZStd::string_view level,
  181. const AZStd::string_view fileName)
  182. {
  183. QStandardItem* rootFolderItem = AddUniqueFolderPath(m_layoutModel->invisibleRootItem(), QString(fileName.data()));
  184. if (rootFolderItem && rootNode)
  185. {
  186. auto controlTypeNode = rootNode->first_node(); // e.g. "AudioTriggers", "AudioRtpcs", etc
  187. while (controlTypeNode)
  188. {
  189. auto controlNode = controlTypeNode->first_node(); // e.g. "ATLTrigger", "ATLRtpc", etc
  190. while (controlNode)
  191. {
  192. LoadControl(controlNode, rootFolderItem, level);
  193. controlNode = controlNode->next_sibling();
  194. }
  195. controlTypeNode = controlTypeNode->next_sibling();
  196. }
  197. }
  198. }
  199. //-------------------------------------------------------------------------------------------//
  200. CATLControl* CAudioControlsLoader::LoadControl(AZ::rapidxml::xml_node<char>* node, QStandardItem* folderItem, const AZStd::string_view scope)
  201. {
  202. CATLControl* control = nullptr;
  203. AZStd::string controlPath;
  204. if (auto controlPathAttr = node->first_attribute("path", 0, false);
  205. controlPathAttr != nullptr)
  206. {
  207. controlPath = controlPathAttr->value();
  208. }
  209. QStandardItem* parentItem = AddUniqueFolderPath(folderItem, QString(controlPath.c_str()));
  210. if (parentItem)
  211. {
  212. AZStd::string name;
  213. if (auto nameAttr = node->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false);
  214. nameAttr != nullptr)
  215. {
  216. name = nameAttr->value();
  217. }
  218. const EACEControlType controlType = TagToType(node->name());
  219. control = m_atlControlsModel->CreateControl(name, controlType);
  220. if (control)
  221. {
  222. QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control);
  223. if (item)
  224. {
  225. parentItem->appendRow(item);
  226. }
  227. switch (controlType)
  228. {
  229. case eACET_SWITCH:
  230. {
  231. auto switchStateNode = node->first_node();
  232. while (switchStateNode)
  233. {
  234. CATLControl* stateControl = LoadControl(switchStateNode, item, scope);
  235. if (stateControl)
  236. {
  237. stateControl->SetParent(control);
  238. control->AddChild(stateControl);
  239. }
  240. switchStateNode = switchStateNode->next_sibling();
  241. }
  242. break;
  243. }
  244. case eACET_PRELOAD:
  245. {
  246. LoadPreloadConnections(node, control);
  247. break;
  248. }
  249. default:
  250. {
  251. LoadConnections(node, control);
  252. break;
  253. }
  254. }
  255. control->SetScope(scope);
  256. }
  257. }
  258. return control;
  259. }
  260. //-------------------------------------------------------------------------------------------//
  261. void CAudioControlsLoader::LoadScopes()
  262. {
  263. AZ::IO::FixedMaxPath levelsFolderPath = AZ::Utils::GetProjectPath();
  264. levelsFolderPath /= "Levels";
  265. LoadScopesImpl(levelsFolderPath.Native());
  266. }
  267. //-------------------------------------------------------------------------------------------//
  268. void CAudioControlsLoader::LoadScopesImpl(const AZStd::string_view levelsFolder)
  269. {
  270. auto fileIO = AZ::IO::FileIOBase::GetInstance();
  271. AZ::IO::FixedMaxPath searchPath{ levelsFolder };
  272. auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*");
  273. for (auto& file : foundFiles)
  274. {
  275. AZ::IO::PathView filePath{ file };
  276. AZ::IO::PathView fileName = filePath.Filename();
  277. if (fileIO->IsDirectory(filePath.Native().data()))
  278. {
  279. LoadScopesImpl((searchPath / fileName).Native());
  280. }
  281. else
  282. {
  283. AZ::IO::PathView fileExt = filePath.Extension();
  284. if (fileExt == ".ly" || fileExt == ".cry" || fileExt == ".prefab")
  285. {
  286. AZ::IO::PathView fileStem = filePath.Stem();
  287. // May need to verify that .prefabs are the actual "level" prefab
  288. // i.e. that it matches levels/<levelname>/<levelname>.prefab
  289. m_atlControlsModel->AddScope(fileStem.Native());
  290. }
  291. }
  292. }
  293. }
  294. //-------------------------------------------------------------------------------------------//
  295. const FilepathSet& CAudioControlsLoader::GetLoadedFilenamesList()
  296. {
  297. return m_loadedFilenames;
  298. }
  299. //-------------------------------------------------------------------------------------------//
  300. void CAudioControlsLoader::CreateDefaultControls()
  301. {
  302. // Load default controls if the don't exist.
  303. // These controls need to always exist in your project
  304. using namespace Audio;
  305. QStandardItem* folderItem = AddFolder(m_layoutModel->invisibleRootItem(), "default_controls");
  306. if (folderItem)
  307. {
  308. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::GetFocusName, eACET_TRIGGER, ""))
  309. {
  310. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::GetFocusName, eACET_TRIGGER), folderItem);
  311. }
  312. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::LoseFocusName, eACET_TRIGGER, ""))
  313. {
  314. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::LoseFocusName, eACET_TRIGGER), folderItem);
  315. }
  316. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::MuteAllName, eACET_TRIGGER, ""))
  317. {
  318. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::MuteAllName, eACET_TRIGGER), folderItem);
  319. }
  320. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::UnmuteAllName, eACET_TRIGGER, ""))
  321. {
  322. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::UnmuteAllName, eACET_TRIGGER), folderItem);
  323. }
  324. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::DoNothingName, eACET_TRIGGER, ""))
  325. {
  326. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::DoNothingName, eACET_TRIGGER), folderItem);
  327. }
  328. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::ObjectSpeedName, eACET_RTPC, ""))
  329. {
  330. AddControl(m_atlControlsModel->CreateControl(ATLInternalControlNames::ObjectSpeedName, eACET_RTPC), folderItem);
  331. }
  332. QStandardItem* switchItem = nullptr;
  333. CATLControl* control = m_atlControlsModel->FindControl(ATLInternalControlNames::ObstructionOcclusionCalcName, eACET_SWITCH, "");
  334. if (control)
  335. {
  336. QModelIndexList indexes = m_layoutModel->match(m_layoutModel->index(0, 0, QModelIndex()), eDR_ID, control->GetId(), 1, Qt::MatchRecursive);
  337. if (!indexes.empty())
  338. {
  339. switchItem = m_layoutModel->itemFromIndex(indexes.at(0));
  340. }
  341. }
  342. else
  343. {
  344. control = m_atlControlsModel->CreateControl(ATLInternalControlNames::ObstructionOcclusionCalcName, eACET_SWITCH);
  345. switchItem = AddControl(control, folderItem);
  346. }
  347. if (switchItem)
  348. {
  349. CATLControl* childControl = nullptr;
  350. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::OOCIgnoreStateName, eACET_SWITCH_STATE, "", control))
  351. {
  352. childControl = CreateInternalSwitchState(control, ATLInternalControlNames::ObstructionOcclusionCalcName, ATLInternalControlNames::OOCIgnoreStateName);
  353. AddControl(childControl, switchItem);
  354. }
  355. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::OOCSingleRayStateName, eACET_SWITCH_STATE, "", control))
  356. {
  357. childControl = CreateInternalSwitchState(control, ATLInternalControlNames::ObstructionOcclusionCalcName, ATLInternalControlNames::OOCSingleRayStateName);
  358. AddControl(childControl, switchItem);
  359. }
  360. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::OOCMultiRayStateName, eACET_SWITCH_STATE, "", control))
  361. {
  362. childControl = CreateInternalSwitchState(control, ATLInternalControlNames::ObstructionOcclusionCalcName, ATLInternalControlNames::OOCMultiRayStateName);
  363. AddControl(childControl, switchItem);
  364. }
  365. }
  366. switchItem = nullptr;
  367. control = m_atlControlsModel->FindControl(ATLInternalControlNames::ObjectVelocityTrackingName, eACET_SWITCH, "");
  368. if (control)
  369. {
  370. QModelIndexList indexes = m_layoutModel->match(m_layoutModel->index(0, 0, QModelIndex()), eDR_ID, control->GetId(), 1, Qt::MatchRecursive);
  371. if (!indexes.empty())
  372. {
  373. switchItem = m_layoutModel->itemFromIndex(indexes.at(0));
  374. }
  375. }
  376. else
  377. {
  378. control = m_atlControlsModel->CreateControl(ATLInternalControlNames::ObjectVelocityTrackingName, eACET_SWITCH);
  379. switchItem = AddControl(control, folderItem);
  380. }
  381. if (switchItem)
  382. {
  383. CATLControl* childControl = nullptr;
  384. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::OVTOnStateName, eACET_SWITCH_STATE, "", control))
  385. {
  386. childControl = CreateInternalSwitchState(control, ATLInternalControlNames::ObjectVelocityTrackingName, ATLInternalControlNames::OVTOnStateName);
  387. AddControl(childControl, switchItem);
  388. }
  389. if (!m_atlControlsModel->FindControl(ATLInternalControlNames::OVTOffStateName, eACET_SWITCH_STATE, "", control))
  390. {
  391. childControl = CreateInternalSwitchState(control, ATLInternalControlNames::ObjectVelocityTrackingName, ATLInternalControlNames::OVTOffStateName);
  392. AddControl(childControl, switchItem);
  393. }
  394. if (!folderItem->hasChildren())
  395. {
  396. m_layoutModel->removeRow(folderItem->row(), m_layoutModel->indexFromItem(folderItem->parent()));
  397. }
  398. }
  399. }
  400. }
  401. //-------------------------------------------------------------------------------------------//
  402. void CAudioControlsLoader::LoadConnections(AZ::rapidxml::xml_node<char>* rootNode, CATLControl* control)
  403. {
  404. if (control && rootNode && m_audioSystemImpl)
  405. {
  406. auto childNode = rootNode->first_node();
  407. while (childNode)
  408. {
  409. TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(childNode, control->GetType());
  410. if (connection)
  411. {
  412. control->AddConnection(connection);
  413. }
  414. control->m_connectionNodes.emplace_back(childNode, connection != nullptr);
  415. childNode = childNode->next_sibling();
  416. }
  417. }
  418. }
  419. //-------------------------------------------------------------------------------------------//
  420. void CAudioControlsLoader::LoadPreloadConnections(AZ::rapidxml::xml_node<char>* node, CATLControl* control)
  421. {
  422. if (!control || !node || !m_audioSystemImpl)
  423. {
  424. return;
  425. }
  426. AZStd::string type;
  427. if (auto typeAttr = node->first_attribute(Audio::ATLXmlTags::ATLTypeAttribute, 0, false);
  428. typeAttr != nullptr)
  429. {
  430. type = typeAttr->value();
  431. }
  432. control->SetAutoLoad(type == Audio::ATLXmlTags::ATLDataLoadType);
  433. auto platformGroupNode = node->first_node(Audio::ATLXmlTags::ATLPlatformsTag, 0, false);
  434. if (platformGroupNode)
  435. {
  436. // Legacy preload parsing...
  437. // Don't parse the platform groups xml chunk anymore.
  438. // Read the connection information for all connected preloads...
  439. auto configGroupNode = node->first_node(Audio::ATLXmlTags::ATLConfigGroupTag, 0, false);
  440. while (configGroupNode)
  441. {
  442. auto connectionNode = configGroupNode->first_node();
  443. while (connectionNode)
  444. {
  445. TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType());
  446. if (connection)
  447. {
  448. control->AddConnection(connection);
  449. }
  450. control->m_connectionNodes.emplace_back(connectionNode, connection != nullptr);
  451. connectionNode = connectionNode->next_sibling();
  452. }
  453. configGroupNode = configGroupNode->next_sibling();
  454. }
  455. }
  456. else
  457. {
  458. // New format preload parsing...
  459. auto connectionNode = node->first_node();
  460. while (connectionNode)
  461. {
  462. TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType());
  463. if (connection)
  464. {
  465. control->AddConnection(connection);
  466. }
  467. control->m_connectionNodes.emplace_back(connectionNode, connection != nullptr);
  468. connectionNode = connectionNode->next_sibling();
  469. }
  470. }
  471. }
  472. //-------------------------------------------------------------------------------------------//
  473. QStandardItem* CAudioControlsLoader::AddControl(CATLControl* control, QStandardItem* folderItem)
  474. {
  475. QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control);
  476. if (item)
  477. {
  478. item->setData(true, eDR_MODIFIED);
  479. folderItem->appendRow(item);
  480. }
  481. return item;
  482. }
  483. //-------------------------------------------------------------------------------------------//
  484. CATLControl* CAudioControlsLoader::CreateInternalSwitchState(CATLControl* parentControl, const AZStd::string& switchName, const AZStd::string& stateName)
  485. {
  486. CATLControl* childControl = m_atlControlsModel->CreateControl(stateName, eACET_SWITCH_STATE, parentControl);
  487. XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
  488. AZ::rapidxml::xml_node<char>* requestNode =
  489. xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLSwitchRequestTag));
  490. AZ::rapidxml::xml_attribute<char>* switchNameAttr = xmlAlloc.allocate_attribute(
  491. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(switchName.c_str()));
  492. requestNode->append_attribute(switchNameAttr);
  493. AZ::rapidxml::xml_node<char>* valueNode =
  494. xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLValueTag));
  495. AZ::rapidxml::xml_attribute<char>* stateNameAttr = xmlAlloc.allocate_attribute(
  496. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(stateName.c_str()));
  497. valueNode->append_attribute(stateNameAttr);
  498. requestNode->append_node(valueNode);
  499. childControl->m_connectionNodes.emplace_back(requestNode, false);
  500. return childControl;
  501. }
  502. } // namespace AudioControls