AudioControlBuilderWorker.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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 <AudioControlBuilderWorker.h>
  9. #include <AzCore/AzCore_Traits_Platform.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <AzCore/JSON/rapidjson.h>
  12. #include <AzCore/JSON/document.h>
  13. #include <AzCore/PlatformId/PlatformId.h>
  14. #include <AzCore/StringFunc/StringFunc.h>
  15. #include <AzFramework/IO/LocalFileIO.h>
  16. #include <ATLCommon.h>
  17. #include <Common_wwise.h>
  18. #include <Config_wwise.h>
  19. namespace AudioControlBuilder
  20. {
  21. namespace Internal
  22. {
  23. const char JsonEventsKey[] = "includedEvents";
  24. const char SoundbankDependencyFileExtension[] = ".bankdeps";
  25. const char NodeDoesNotExistMessage[] = "%s node does not exist. Please be sure that you have defined at least one %s for this Audio Control file.\n";
  26. const char MalformedNodeMissingAttributeMessage[] = "%s node is malformed: does not have an attribute %s defined. This is likely the result of manual editing. Please resave the Audio Control file.\n";
  27. const char MalformedNodeMissingChildNodeMessage[] = "%s node does not contain a child %s node. This is likely the result of manual editing. Please resave the Audio Control file.\n";
  28. namespace Legacy
  29. {
  30. using AtlConfigGroupMap = AZStd::unordered_map<AZStd::string, const AZ::rapidxml::xml_node<char>*>;
  31. AZStd::string GetAtlPlatformName(const AZStd::string& requestPlatform)
  32. {
  33. AZStd::string atlPlatform;
  34. AZStd::string platform = requestPlatform;
  35. // When debugging a builder using a debug task, it replaces platform tags with "debug platform". Make sure the builder
  36. // actually uses the host platform identifier when going through this function in this case.
  37. if (platform == "debug platform")
  38. {
  39. atlPlatform = AZ_TRAIT_OS_PLATFORM_NAME;
  40. AZStd::to_lower(atlPlatform.begin(), atlPlatform.end());
  41. }
  42. else
  43. {
  44. if (platform == "pc")
  45. {
  46. atlPlatform = "windows";
  47. }
  48. else if (platform == "android")
  49. {
  50. atlPlatform = "android";
  51. }
  52. else if (platform == "mac")
  53. {
  54. atlPlatform = "mac";
  55. }
  56. else
  57. {
  58. atlPlatform = AZStd::move(platform);
  59. }
  60. }
  61. return AZStd::move(atlPlatform);
  62. }
  63. AZ::Outcome<void, AZStd::string> BuildConfigGroupMap(const AZ::rapidxml::xml_node<char>* preloadRequestNode, AtlConfigGroupMap& configGroupMap)
  64. {
  65. AZ_Assert(preloadRequestNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request");
  66. auto configGroupNode = preloadRequestNode->first_node(Audio::ATLXmlTags::ATLConfigGroupTag);
  67. while (configGroupNode)
  68. {
  69. // Populate the config group map by iterating over all ATLConfigGroup nodes, and place each one in the map keyed by the group's atl_name attribute...
  70. if (const auto configGroupNameAttr = configGroupNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute))
  71. {
  72. configGroupMap.emplace(configGroupNameAttr->value(), configGroupNode);
  73. }
  74. else
  75. {
  76. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  77. Audio::ATLXmlTags::ATLConfigGroupTag, Audio::ATLXmlTags::ATLNameAttribute));
  78. }
  79. configGroupNode = configGroupNode->next_sibling(Audio::ATLXmlTags::ATLConfigGroupTag);
  80. }
  81. // If no config groups are defined, this is an empty preload request with no banks referenced, which is valid.
  82. return AZ::Success();
  83. }
  84. AZ::Outcome<void, AZStd::string> GetBanksFromAtlPreloads(const AZ::rapidxml::xml_node<char>* preloadsNode, const AZStd::string& atlPlatformIdentifier, AZStd::vector<AZStd::string>& banksReferenced)
  85. {
  86. AZ_Assert(preloadsNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::PreloadsNodeTag, "preload request");
  87. auto preloadRequestNode = preloadsNode->first_node(Audio::ATLXmlTags::ATLPreloadRequestTag);
  88. if (!preloadRequestNode)
  89. {
  90. return AZ::Failure(AZStd::string::format(NodeDoesNotExistMessage,
  91. Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request"));
  92. }
  93. // For each preload request in the control file, determine which config group is used for this platform and register each
  94. // bank listed in that preload request as a dependency.
  95. while (preloadRequestNode)
  96. {
  97. AtlConfigGroupMap configGroupMap;
  98. auto configGroupMapResult = BuildConfigGroupMap(preloadRequestNode, configGroupMap);
  99. // If the returned map is empty, there are not banks referenced in the preload request, return the result here.
  100. if (!configGroupMapResult.IsSuccess() || configGroupMap.size() == 0)
  101. {
  102. return configGroupMapResult;
  103. }
  104. const auto platformsNode = preloadRequestNode->first_node(Audio::ATLXmlTags::ATLPlatformsTag);
  105. if (!platformsNode)
  106. {
  107. return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
  108. Audio::ATLXmlTags::ATLPreloadRequestTag, Audio::ATLXmlTags::ATLPlatformsTag));
  109. }
  110. auto platformNode = platformsNode->first_node(Audio::ATLXmlTags::PlatformNodeTag);
  111. if (!platformNode)
  112. {
  113. return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
  114. Audio::ATLXmlTags::ATLPlatformsTag, Audio::ATLXmlTags::PlatformNodeTag));
  115. }
  116. AZStd::string configGroupName;
  117. // For each platform node in the platform list, check the atl_name to see if it matches the platform the request is
  118. // intended for. If it is, grab the name of the config group that is used for that platform to load it.
  119. while (platformNode)
  120. {
  121. const auto atlNameAttr = platformNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute);
  122. if (!atlNameAttr)
  123. {
  124. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  125. Audio::ATLXmlTags::PlatformNodeTag, Audio::ATLXmlTags::ATLNameAttribute));
  126. }
  127. else if (atlPlatformIdentifier == atlNameAttr->value())
  128. {
  129. // We've found the right platform that matches the request, so grab the group name and stop looking through
  130. // the list
  131. const auto configGroupNameAttr = platformNode->first_attribute(Audio::ATLXmlTags::ATLConfigGroupAttribute);
  132. if (!configGroupNameAttr)
  133. {
  134. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  135. Audio::ATLXmlTags::PlatformNodeTag, Audio::ATLXmlTags::ATLConfigGroupAttribute));
  136. }
  137. configGroupName = configGroupNameAttr->value();
  138. break;
  139. }
  140. platformNode = platformNode->next_sibling(Audio::ATLXmlTags::PlatformNodeTag);
  141. }
  142. const AZ::rapidxml::xml_node<char>* configGroupNode = configGroupMap[configGroupName];
  143. if (!configGroupNode)
  144. {
  145. // The config group this platform uses isn't defined in the control file. This might be intentional, so just
  146. // generate a warning and keep going to the next preload node.
  147. AZ_TracePrintf("Audio Control Builder", "%s node for config group %s is not defined, so no banks are referenced.",
  148. Audio::ATLXmlTags::ATLConfigGroupTag, configGroupName.c_str());
  149. }
  150. else
  151. {
  152. auto wwiseFileNode = configGroupNode->first_node(Audio::WwiseXmlTags::WwiseFileTag);
  153. if (!wwiseFileNode)
  154. {
  155. return AZ::Failure(AZStd::string::format(MalformedNodeMissingChildNodeMessage,
  156. Audio::ATLXmlTags::ATLConfigGroupTag, Audio::WwiseXmlTags::WwiseFileTag));
  157. }
  158. // For each WwiseFile (soundbank) referenced in the config group, grab the file name and add it to the reference list
  159. while (wwiseFileNode)
  160. {
  161. const auto bankNameAttribute = wwiseFileNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute);
  162. if (!bankNameAttribute)
  163. {
  164. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  165. Audio::WwiseXmlTags::WwiseFileTag, Audio::WwiseXmlTags::WwiseNameAttribute));
  166. }
  167. // Prepend the bank name with the relative path to the wwise sounds folder to get relative path to the bank from
  168. // the @products@ alias and push that into the list of banks referenced.
  169. AZStd::string soundsPrefix = Audio::Wwise::DefaultBanksPath;
  170. banksReferenced.emplace_back(soundsPrefix + bankNameAttribute->value());
  171. wwiseFileNode = wwiseFileNode->next_sibling(Audio::WwiseXmlTags::WwiseFileTag);
  172. }
  173. }
  174. preloadRequestNode = preloadRequestNode->next_sibling(Audio::ATLXmlTags::ATLPreloadRequestTag);
  175. }
  176. return AZ::Success();
  177. }
  178. } // namespace Legacy
  179. AZ::Outcome<void, AZStd::string> BuildAtlEventList(const AZ::rapidxml::xml_node<char>* triggersNode, AZStd::vector<AZStd::string>& eventNames)
  180. {
  181. AZ_Assert(triggersNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::TriggersNodeTag, "trigger");
  182. auto triggerNode = triggersNode->first_node(Audio::ATLXmlTags::ATLTriggerTag);
  183. while (triggerNode)
  184. {
  185. // For each audio trigger, push the name of the Wwise event (if assigned) into the list.
  186. // It's okay for an ATLTrigger node to not have a Wwise event associated with it.
  187. if (const auto eventNode = triggerNode->first_node(Audio::WwiseXmlTags::WwiseEventTag))
  188. {
  189. if (const auto eventNameAttr = eventNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute))
  190. {
  191. eventNames.push_back(eventNameAttr->value());
  192. }
  193. else
  194. {
  195. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  196. Audio::WwiseXmlTags::WwiseEventTag, Audio::WwiseXmlTags::WwiseNameAttribute));
  197. }
  198. }
  199. triggerNode = triggerNode->next_sibling(Audio::ATLXmlTags::ATLTriggerTag);
  200. }
  201. return AZ::Success();
  202. }
  203. AZ::Outcome<void, AZStd::string> GetBanksFromAtlPreloads(const AZ::rapidxml::xml_node<char>* preloadsNode, AZStd::vector<AZStd::string>& banksReferenced)
  204. {
  205. AZ_Assert(preloadsNode != nullptr, NodeDoesNotExistMessage, Audio::ATLXmlTags::PreloadsNodeTag, "preload request");
  206. auto preloadRequestNode = preloadsNode->first_node(Audio::ATLXmlTags::ATLPreloadRequestTag);
  207. if (!preloadRequestNode)
  208. {
  209. return AZ::Failure(AZStd::string::format(NodeDoesNotExistMessage, Audio::ATLXmlTags::ATLPreloadRequestTag, "preload request"));
  210. }
  211. // Loop through the ATLPreloadRequest nodes...
  212. // Find any Wwise banks listed and add them to the banksReferenced vector.
  213. while (preloadRequestNode)
  214. {
  215. // Attempt to find the child node in the New Xml format...
  216. if (auto wwiseFileNode = preloadRequestNode->first_node(Audio::WwiseXmlTags::WwiseFileTag))
  217. {
  218. while (wwiseFileNode)
  219. {
  220. const auto bankNameAttr = wwiseFileNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute);
  221. if (bankNameAttr)
  222. {
  223. AZStd::string soundsPrefix = Audio::Wwise::DefaultBanksPath;
  224. banksReferenced.emplace_back(soundsPrefix + bankNameAttr->value());
  225. }
  226. else
  227. {
  228. return AZ::Failure(AZStd::string::format(MalformedNodeMissingAttributeMessage,
  229. Audio::WwiseXmlTags::WwiseFileTag, Audio::WwiseXmlTags::WwiseNameAttribute));
  230. }
  231. wwiseFileNode = wwiseFileNode->next_sibling(Audio::WwiseXmlTags::WwiseFileTag);
  232. }
  233. }
  234. else
  235. {
  236. return AZ::Failure(AZStd::string::format("Preloads Xml appears to be in an older format, trying Legacy parsing.\n"));
  237. }
  238. preloadRequestNode = preloadRequestNode->next_sibling(Audio::ATLXmlTags::ATLPreloadRequestTag);
  239. }
  240. return AZ::Success();
  241. }
  242. AZ::Outcome<void, AZStd::string> GetEventsFromBankMetadata(const rapidjson::Value& rootObject, AZStd::set<AZStd::string>& eventNames)
  243. {
  244. if (!rootObject.IsObject())
  245. {
  246. return AZ::Failure(AZStd::string("The root of the metadata file is not an object. Please regenerate the metadata for this soundbank."));
  247. }
  248. // If the file doesn't define an events field, then there are no events in the bank
  249. if (!rootObject.HasMember(JsonEventsKey))
  250. {
  251. return AZ::Success();
  252. }
  253. const rapidjson::Value& eventsArray = rootObject[JsonEventsKey];
  254. if (!eventsArray.IsArray())
  255. {
  256. return AZ::Failure(AZStd::string("Events field is not an array. Please regenerate the metadata for this soundbank."));
  257. }
  258. for (rapidjson::SizeType eventIndex = 0; eventIndex < eventsArray.Size(); ++eventIndex)
  259. {
  260. eventNames.emplace(eventsArray[eventIndex].GetString());
  261. }
  262. return AZ::Success();
  263. }
  264. AZ::Outcome<void, AZStd::string> GetEventsFromBank(const AZStd::string& bankMetadataPath, AZStd::set<AZStd::string>& eventNames)
  265. {
  266. if (!AZ::IO::SystemFile::Exists(bankMetadataPath.c_str()))
  267. {
  268. return AZ::Failure(AZStd::string::format("Failed to find the soundbank metadata file %s. Full dependency information cannot be determined without the metadata file. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
  269. }
  270. AZ::u64 fileSize = AZ::IO::SystemFile::Length(bankMetadataPath.c_str());
  271. if (fileSize == 0)
  272. {
  273. return AZ::Failure(AZStd::string::format("Soundbank metadata file at path %s is an empty file. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
  274. }
  275. AZStd::vector<char> buffer(fileSize + 1);
  276. buffer[fileSize] = 0;
  277. if (!AZ::IO::SystemFile::Read(bankMetadataPath.c_str(), buffer.data()))
  278. {
  279. return AZ::Failure(AZStd::string::format("Failed to read the soundbank metadata file at path %s. Please make sure the file is not open or being edited by another program.", bankMetadataPath.c_str()));
  280. }
  281. rapidjson::Document bankMetadataDoc;
  282. bankMetadataDoc.Parse(buffer.data());
  283. if (bankMetadataDoc.GetParseError() != rapidjson::ParseErrorCode::kParseErrorNone)
  284. {
  285. return AZ::Failure(AZStd::string::format("Failed to parse soundbank metadata at path %s into JSON. Please regenerate the metadata for this soundbank.", bankMetadataPath.c_str()));
  286. }
  287. return GetEventsFromBankMetadata(bankMetadataDoc, eventNames);
  288. }
  289. } // namespace Internal
  290. AudioControlBuilderWorker::AudioControlBuilderWorker()
  291. : m_globalScopeControlsPath("libs/gameaudio/")
  292. , m_isShuttingDown(false)
  293. {
  294. AZ::StringFunc::Path::Normalize(m_globalScopeControlsPath);
  295. }
  296. void AudioControlBuilderWorker::ShutDown()
  297. {
  298. m_isShuttingDown = true;
  299. }
  300. void AudioControlBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  301. {
  302. if (m_isShuttingDown)
  303. {
  304. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  305. return;
  306. }
  307. for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
  308. {
  309. if (info.m_identifier == "server")
  310. {
  311. continue;
  312. }
  313. AssetBuilderSDK::JobDescriptor descriptor;
  314. descriptor.m_jobKey = "Audio Control";
  315. descriptor.m_critical = true;
  316. descriptor.SetPlatformIdentifier(info.m_identifier.c_str());
  317. descriptor.m_priority = 0;
  318. response.m_createJobOutputs.push_back(descriptor);
  319. }
  320. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  321. }
  322. void AudioControlBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  323. {
  324. AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "AudioControlBuilderWorker Starting Job.\n");
  325. if (m_isShuttingDown)
  326. {
  327. AZ_TracePrintf(AssetBuilderSDK::WarningWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
  328. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  329. return;
  330. }
  331. AZStd::string fileName;
  332. AZ::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), fileName);
  333. AssetBuilderSDK::JobProduct jobProduct(request.m_fullPath);
  334. if (!ParseProductDependencies(request, jobProduct.m_dependencies, jobProduct.m_pathDependencies))
  335. {
  336. AZ_Error(AssetBuilderSDK::ErrorWindow, false, "Error during parsing product dependencies for asset %s.\n", fileName.c_str());
  337. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  338. return;
  339. }
  340. response.m_outputProducts.push_back(jobProduct);
  341. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  342. }
  343. bool AudioControlBuilderWorker::ParseProductDependencies(
  344. const AssetBuilderSDK::ProcessJobRequest& request,
  345. AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies,
  346. AssetBuilderSDK::ProductPathDependencySet& pathDependencies)
  347. {
  348. AZ::IO::FileIOStream fileStream;
  349. if (!fileStream.Open(request.m_fullPath.c_str(), AZ::IO::OpenMode::ModeRead))
  350. {
  351. return false;
  352. }
  353. AZ::IO::SizeType length = fileStream.GetLength();
  354. if (length == 0)
  355. {
  356. return false;
  357. }
  358. AZStd::vector<char> charBuffer;
  359. charBuffer.resize_no_construct(length + 1);
  360. fileStream.Read(length, charBuffer.data());
  361. charBuffer.back() = 0;
  362. // Get the XML root node
  363. AZ::rapidxml::xml_document<char> xmlDoc;
  364. if (!xmlDoc.parse<AZ::rapidxml::parse_no_data_nodes>(charBuffer.data()))
  365. {
  366. return false;
  367. }
  368. AZ::rapidxml::xml_node<char>* xmlRootNode = xmlDoc.first_node();
  369. if (!xmlRootNode)
  370. {
  371. return false;
  372. }
  373. ParseProductDependenciesFromXmlFile(xmlRootNode,
  374. request.m_fullPath,
  375. request.m_sourceFile,
  376. request.m_platformInfo.m_identifier,
  377. productDependencies,
  378. pathDependencies);
  379. return true;
  380. }
  381. void AudioControlBuilderWorker::ParseProductDependenciesFromXmlFile(
  382. const AZ::rapidxml::xml_node<char>* node,
  383. const AZStd::string& fullPath,
  384. [[maybe_unused]] const AZStd::string& sourceFile,
  385. const AZStd::string& platformIdentifier,
  386. [[maybe_unused]] AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies,
  387. AssetBuilderSDK::ProductPathDependencySet& pathDependencies)
  388. {
  389. AZ_Assert(node != nullptr, "AudioControlBuilderWorker::ParseProductDependenciesFromXmlFile - null xml root node!\n");
  390. const auto preloadsNode = node->first_node(Audio::ATLXmlTags::PreloadsNodeTag);
  391. if (!preloadsNode)
  392. {
  393. // No preloads were defined in this control file, so we can return. If triggers are defined in this preload file, we can't
  394. // validate that they'll be playable because we are unsure of what other control files for the given scope are defined.
  395. return;
  396. }
  397. // Collect any references to soundbanks, initially use the newer parsing format...
  398. AZStd::vector<AZStd::string> banksReferenced;
  399. AZ::Outcome<void, AZStd::string> gatherBankReferencesResult = Internal::GetBanksFromAtlPreloads(preloadsNode, banksReferenced);
  400. if (!gatherBankReferencesResult)
  401. {
  402. // Legacy...
  403. // Convert platform name to platform name that is used by wwise and ATL.
  404. AZStd::string atlPlatformName = AZStd::move(Internal::Legacy::GetAtlPlatformName(platformIdentifier));
  405. gatherBankReferencesResult = Internal::Legacy::GetBanksFromAtlPreloads(preloadsNode, atlPlatformName, banksReferenced);
  406. }
  407. if (!gatherBankReferencesResult)
  408. {
  409. AZ_Warning("Audio Control Builder", false, "Failed to gather product dependencies for Audio Control file %s. %s\n",
  410. sourceFile.c_str(), gatherBankReferencesResult.GetError().c_str());
  411. return;
  412. }
  413. if (banksReferenced.size() == 0)
  414. {
  415. // If there are no banks referenced, then there are no dependencies to register, so return.
  416. return;
  417. }
  418. for (const AZStd::string& relativeBankPath : banksReferenced)
  419. {
  420. pathDependencies.emplace(relativeBankPath, AssetBuilderSDK::ProductPathDependencyType::ProductFile);
  421. }
  422. // For each bank figure out what events are included in the bank, then run through every event referenced in the file and
  423. // make sure it is in the list gathered from the banks.
  424. const auto triggersNode = node->first_node(Audio::ATLXmlTags::TriggersNodeTag);
  425. if (!triggersNode)
  426. {
  427. // No triggers were defined in this file, so we don't need to do any event validation.
  428. return;
  429. }
  430. AZStd::vector<AZStd::string> eventsReferenced;
  431. AZ::Outcome<void, AZStd::string> gatherEventReferencesResult = Internal::BuildAtlEventList(triggersNode, eventsReferenced);
  432. if (!gatherEventReferencesResult.IsSuccess())
  433. {
  434. AZ_Warning("Audio Control Builder", false, "Failed to gather list of events referenced by Audio Control file %s. %s",
  435. sourceFile.c_str(), gatherEventReferencesResult.GetError().c_str());
  436. return;
  437. }
  438. AZStd::string projectSourcePath = fullPath;
  439. AZ::u64 firstSubDirectoryIndex = AZ::StringFunc::Find(projectSourcePath, m_globalScopeControlsPath);
  440. AZ::StringFunc::LKeep(projectSourcePath, firstSubDirectoryIndex);
  441. AZStd::set<AZStd::string> wwiseEventsInReferencedBanks;
  442. // Load all bankdeps files for all banks referenced and aggregate the list of events in those files.
  443. for (const AZStd::string& relativeBankPath : banksReferenced)
  444. {
  445. // Create the full path to the bankdeps file from the bank file.
  446. AZStd::string bankMetadataPath;
  447. AZ::StringFunc::Path::Join(projectSourcePath.c_str(), relativeBankPath.c_str(), bankMetadataPath);
  448. AZ::StringFunc::Path::ReplaceExtension(bankMetadataPath, Internal::SoundbankDependencyFileExtension);
  449. AZ::Outcome<void, AZStd::string> getReferencedEventsResult = Internal::GetEventsFromBank(bankMetadataPath, wwiseEventsInReferencedBanks);
  450. if (!getReferencedEventsResult.IsSuccess())
  451. {
  452. // only warn if we couldn't get info from a bankdeps file. Won't impact registering dependencies, but used to help
  453. // customers potentially debug issues.
  454. AZ_Warning("Audio Control Builder", false, "Failed to gather list of events referenced by soundbank %s. %s", relativeBankPath.c_str(), getReferencedEventsResult.GetError().c_str());
  455. }
  456. }
  457. // Confirm that each event referenced by the file exists in the list of events available from the banks referenced.
  458. for (const AZStd::string& eventInControlFile : eventsReferenced)
  459. {
  460. if (wwiseEventsInReferencedBanks.find(eventInControlFile) == wwiseEventsInReferencedBanks.end())
  461. {
  462. AZ_Warning("Audio Control Builder", false, "Failed to find Wwise event %s in the list of events contained in banks referenced by %s. Event may fail to play properly.", eventInControlFile.c_str(), sourceFile.c_str());
  463. }
  464. }
  465. }
  466. } // namespace AudioControlBuilder