From a0d58d40ea35ee0ce00cc7889ee814780cb323cc Mon Sep 17 00:00:00 2001 From: ArthurHeitmann <37270165+ArthurHeitmann@users.noreply.github.com> Date: Sat, 31 Aug 2024 00:46:25 +0200 Subject: [PATCH] multi bnk project generation --- lib/fileTypeUtils/audio/bnkIO.dart | 4 +- .../wwiseProjectGenerator/bnkLoader.dart | 332 ++++++++++++++++++ .../elements/hierarchyBaseElements.dart | 21 +- .../elements/wwiseAttenuations.dart | 6 +- .../elements/wwiseAudioFileSource.dart | 4 +- .../elements/wwiseBus.dart | 11 +- .../elements/wwiseConversionInfo.dart | 5 +- .../elements/wwiseEffect.dart | 5 +- .../elements/wwiseEvents.dart | 33 +- .../elements/wwiseGameParameters.dart | 10 +- .../elements/wwiseMusicSwitch.dart | 3 +- .../elements/wwiseSound.dart | 4 +- .../elements/wwiseSoundBanks.dart | 71 ++-- .../elements/wwiseSourcePlugin.dart | 8 +- .../elements/wwiseStateInfo.dart | 2 +- .../elements/wwiseStates.dart | 9 +- .../elements/wwiseSwitches.dart | 14 +- .../elements/wwiseTransitionList.dart | 12 +- .../elements/wwiseTriggers.dart | 11 +- .../elements/wwiseWorkUnit.dart | 23 +- .../wwiseAudioFilesPrepare.dart | 143 -------- .../wwiseProjectGenerator/wwiseElement.dart | 3 +- .../wwiseElementBase.dart | 23 +- .../wwiseIdGenerator.dart | 2 +- .../wwiseProjectGenerator.dart | 104 +----- .../wwiseProjectGenerator/wwiseUtils.dart | 9 + .../misc/wwiseProjectGeneratorPopup.dart | 109 +++++- 27 files changed, 631 insertions(+), 350 deletions(-) create mode 100644 lib/utils/wwiseProjectGenerator/bnkLoader.dart delete mode 100644 lib/utils/wwiseProjectGenerator/wwiseAudioFilesPrepare.dart diff --git a/lib/fileTypeUtils/audio/bnkIO.dart b/lib/fileTypeUtils/audio/bnkIO.dart index b01d1e9..134b968 100644 --- a/lib/fileTypeUtils/audio/bnkIO.dart +++ b/lib/fileTypeUtils/audio/bnkIO.dart @@ -189,7 +189,7 @@ class BnkWemFileInfo { } class BnkDataChunk extends BnkChunkBase { - List> wemFiles = []; + List wemFiles = []; BnkDataChunk(super.chunkId, super.chunkSize, this.wemFiles); @@ -197,7 +197,7 @@ class BnkDataChunk extends BnkChunkBase { int initialPosition = bytes.position; for (var file in didx.files) { bytes.position = initialPosition + file.offset; - wemFiles.add(bytes.readUint8List(file.size)); + wemFiles.add(bytes.asUint8List(file.size)); } int remaining = chunkSize - (bytes.position - initialPosition); if (remaining > 0) { diff --git a/lib/utils/wwiseProjectGenerator/bnkLoader.dart b/lib/utils/wwiseProjectGenerator/bnkLoader.dart new file mode 100644 index 0000000..f4dba3b --- /dev/null +++ b/lib/utils/wwiseProjectGenerator/bnkLoader.dart @@ -0,0 +1,332 @@ + +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:path/path.dart'; + +import '../../background/wemFilesIndexer.dart'; +import '../../fileTypeUtils/audio/bnkIO.dart'; +import '../../fileTypeUtils/audio/wemIdsToNames.dart'; +import '../../fileTypeUtils/audio/wemToWavConverter.dart'; +import '../../fileTypeUtils/utils/ByteDataWrapper.dart'; +import '../utils.dart'; +import 'wwiseIdGenerator.dart'; +import 'wwiseProjectGenerator.dart'; +import 'wwiseUtils.dart'; + +class WwiseAudioFile { + final int id; + final int? prefetchId; + final String name; + final String path; + final String language; + final bool isVoice; + int _idsGenerated = 0; + + WwiseAudioFile(this.id, this.prefetchId, this.name, this.path, this.language, this.isVoice); + + int nextWemId(WwiseIdGenerator idGen) { + if (_idsGenerated == 0) { + _idsGenerated++; + return id; + } + if (_idsGenerated == 1) { + _idsGenerated++; + if (prefetchId != null && prefetchId! > id) + return prefetchId!; + } + return idGen.wemId(min: id + 1); + } +} + +class BnkContext { + final String path; + final String name; + final String language; + final T value; + + BnkContext(this.path, this.name, this.language, this.value); + + BnkContext cast() => BnkContext(path, name, language, value as U); +} + +class _AudioFileInfo { + final int id; + final int? prefetchId; + final int streamType; + final bool isVoice; + + _AudioFileInfo(this.id, this.prefetchId, this.streamType, this.isVoice); +} + +Future loadBnks( + WwiseProjectGenerator project, + List bnkPaths, + List outBnkNames, + List> outHircChunks, + Map> outHircChunksById, + Map outSoundFiles +) async { + Map> usedAudioFiles = {}; + Map> inMemoryWemIds = {}; + Set visitedBnkIds = {}; + + for (var (i, bnkPath) in bnkPaths.indexed) { + if (bnkPaths.length > 1) + project.status.currentMsg.value = "Pre processing BNK file $i / ${bnkPaths.length} (${basename(bnkPath)})"; + + var bnk = BnkFile.read(await ByteDataWrapper.fromFile(bnkPath)); + if (bnk.chunks.length == 1) + continue; + var header = bnk.chunks.whereType().first; + var language = _languageIds[header.languageId] ?? "SFX"; + var bnkId = header.bnkId; + var bnkName = wemIdsToNames[bnkId] ?? basename(bnkPath); + outBnkNames.add(bnkName); + if (visitedBnkIds.contains(bnkId)) { + project.log(WwiseLogSeverity.warning, "Found ${basename(bnkPath)} more than once"); + continue; + } + visitedBnkIds.add(bnkId); + + var didx = bnk.chunks.whereType().firstOrNull; + var data = bnk.chunks.whereType().firstOrNull; + if ((didx == null) != (data == null)) { + throw Exception("BNK file has only one of DIDX and DATA chunks"); + } + // index in memory WEM files + if (didx != null && data != null) { + for (var (i, info) in didx.files.indexed) { + var wemId = info.id; + inMemoryWemIds[wemId] = BnkContext(bnkPath, "", language, (index: i)); + } + } + + // collect all hirc chunks and used audio files + var hirc = bnk.chunks.whereType().firstOrNull; + if (hirc != null) { + for (var chunk in hirc.chunks) { + var existing = outHircChunksById[chunk.uid]; + if (existing != null) { + if (existing.value.size != chunk.size) + project.log(WwiseLogSeverity.warning, "Found chunk ${chunk.uid} (${chunk.size} B) (${basename(bnkPath)}) more than once. Using largest chunk"); + if (chunk.size <= existing.value.size) + continue; + } + var hircContext = BnkContext(bnkPath, bnkName, language, chunk); + outHircChunksById[chunk.uid] = hircContext; + + // collect all used audio files + List<({int sourceId, int fileId, int streamType, bool isVoice})> sources = []; + if (chunk is BnkSound) { + sources.add(( + sourceId: chunk.bankData.mediaInformation.sourceID, + fileId: chunk.bankData.mediaInformation.uFileID, + streamType: chunk.bankData.streamType, + isVoice: chunk.bankData.mediaInformation.uSourceBits & 1 != 0 + )); + } + else if (chunk is BnkMusicTrack) { + for (var src in chunk.sources) { + sources.add(( + sourceId: src.sourceID, + fileId: src.fileID, + streamType: src.streamType, + isVoice: src.uSourceBits & 1 != 0 + )); + } + } + for (var src in sources) { + if (src.fileId == 0) + continue; + if (src.streamType == 0) { + usedAudioFiles[src.sourceId] = BnkContext(bnkPath, "", language, _AudioFileInfo(src.sourceId, null, src.streamType, src.isVoice)); + } else if (src.streamType == 1) { + usedAudioFiles[src.fileId] = BnkContext(bnkPath, "", language, _AudioFileInfo(src.fileId, null, src.streamType, src.isVoice)); + } else if (src.streamType == 2) { + usedAudioFiles[src.fileId] = BnkContext(bnkPath, "", language, _AudioFileInfo(src.fileId, src.sourceId, src.streamType, src.isVoice)); + } else { + project.log(WwiseLogSeverity.warning, "Unknown stream type ${src.streamType}"); + } + } + } + } + } + + outHircChunks.addAll(outHircChunksById.values); + + // add all used audio files to project + if (project.options.wems) + await _loadWems(usedAudioFiles, project, inMemoryWemIds, outSoundFiles); +} + +Future _loadWems(Map> usedAudioFiles, WwiseProjectGenerator project, Map> inMemoryWemIds, Map outSoundFiles) async { + var usedLanguages = usedAudioFiles.values.map((f) => f.language).toSet(); + Map langPaths = {}; + for (var language in usedLanguages) { + String langPath; + if (language == "SFX") + langPath = join(project.projectPath, "Originals", "SFX"); + else + langPath = join(project.projectPath, "Originals", "Voices", language); + await Directory(langPath).create(recursive: true); + langPaths[language] = langPath; + } + var tempDir = await Directory.systemTemp.createTemp("wem_conversion"); + var parallel = max(2, Platform.numberOfProcessors ~/ 2); + var bnkCache = _BnkCache(parallel + 1); + var processed = 0; + try { + await futuresWaitBatched(usedAudioFiles.values.map((audioContext) async { + processed++; + project.status.currentMsg.value = "Processing WEM files $processed / ${usedAudioFiles.length}"; + // get wem path + String? wemPath; + Uint8List? wemBytes; + var streamType = audioContext.value.streamType; + if (streamType == 0) { + var audioInfo = inMemoryWemIds[audioContext.value.id]; + if (audioInfo == null) { + project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(audioContext.value.id, alwaysIncludeId: true)} is not found in any BNK file"); + return; + } + var bnkFiles = await bnkCache.get(audioInfo.path); + var index = audioInfo.value.index; + wemBytes = bnkFiles[index]; + } + else if (streamType == 1) { + wemPath = wemFilesLookup.lookup[audioContext.value.id]; + if (wemPath == null) { + project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(audioContext.value.id, alwaysIncludeId: true)} is not indexed"); + return; + } + } + else if (streamType == 2) { + wemPath = wemFilesLookup.lookup[audioContext.value.id]; + if (wemPath == null) { + var audioInfo = inMemoryWemIds[audioContext.value.prefetchId!]; + if (audioInfo == null) { + project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(audioContext.value.id, alwaysIncludeId: true)} is not indexed and prefetch file ${wwiseIdToStr(audioContext.value.prefetchId!, alwaysIncludeId: true)} is not found in any BNK file"); + return; + } + project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(audioContext.value.id, alwaysIncludeId: true)} is not indexed, using prefetch file ${wwiseIdToStr(audioContext.value.prefetchId!, alwaysIncludeId: true)}"); + var bnkFiles = await bnkCache.get(audioInfo.path); + var index = audioInfo.value.index; + wemBytes = bnkFiles[index]; + } + } + if (wemPath == null) { + wemPath = join(tempDir.path, "${audioContext.value.id}.wem"); + await File(wemPath).writeAsBytes(wemBytes!); + } + + // save to project as WAV + var langFolder = langPaths[audioContext.language]!; + var wavPath = join(langFolder, "${wwiseIdToStr(audioContext.value.id)}.wav"); + if (!await File(wavPath).exists()) + await wemToWav(wemPath, wavPath); + + var audioFile = WwiseAudioFile(audioContext.value.id, audioContext.value.prefetchId, wwiseIdToStr(audioContext.value.id), wavPath, audioContext.language, audioContext.value.isVoice); + outSoundFiles[audioContext.value.id] = audioFile; + if (audioContext.value.prefetchId != null) + outSoundFiles[audioContext.value.prefetchId!] = audioFile; + }), parallel); + } finally { + await tempDir.delete(recursive: true); + } +} + +class _BnkCacheFile { + int lastUsed; + final String path; + final List wems; + + _BnkCacheFile(this.lastUsed, this.path, this.wems); +} + +class _BnkCache { + final List<_BnkCacheFile> _files = []; + final int maxBnks; + + _BnkCache(this.maxBnks); + + Future> get(String path) async { + var bnk = _get(path); + if (bnk != null) { + bnk.lastUsed = _now(); + return bnk.wems; + } + _makeSpace(); + var bnkFile = BnkFile.read(await ByteDataWrapper.fromFile(path)); + var data = bnkFile.chunks.whereType().firstOrNull; + var wems = data?.wemFiles ?? []; + var cacheFile = _BnkCacheFile(_now(), path, wems); + _files.add(cacheFile); + return wems; + } + + _BnkCacheFile? _get(String path) { + return _files + .where((b) => b.path == path) + .firstOrNull; + } + + int _now() => DateTime.now().microsecondsSinceEpoch; + + void _makeSpace() { + if (_files.length < maxBnks) + return; + int oldestUsed = _now(); + int oldestIndex = 0; + for (var (i, b) in _files.indexed) { + if (b.lastUsed < oldestUsed) { + oldestUsed = b.lastUsed; + oldestIndex = i; + } + } + _files.removeAt(oldestIndex); + } +} + +const _languageIds = { + 0x00: "SFX", + 0x01: "Arabic", + 0x02: "Bulgarian", + 0x03: "Chinese(HK)", + 0x04: "Chinese(PRC)", + 0x05: "Chinese(Taiwan)", + 0x06: "Czech", + 0x07: "Danish", + 0x08: "Dutch", + 0x09: "English(Australia)", + 0x0A: "English(India)", + 0x0B: "English(UK)", + 0x0C: "English(US)", + 0x0D: "Finnish", + 0x0E: "French(Canada)", + 0x0F: "French(France)", + 0x10: "German", + 0x11: "Greek", + 0x12: "Hebrew", + 0x13: "Hungarian", + 0x14: "Indonesian", + 0x15: "Italian", + 0x16: "Japanese", + 0x17: "Korean", + 0x18: "Latin", + 0x19: "Norwegian", + 0x1A: "Polish", + 0x1B: "Portuguese(Brazil)", + 0x1C: "Portuguese(Portugal)", + 0x1D: "Romanian", + 0x1E: "Russian", + 0x1F: "Slovenian", + 0x20: "Spanish(Mexico)", + 0x21: "Spanish(Spain)", + 0x22: "Spanish(US)", + 0x23: "Swedish", + 0x24: "Turkish", + 0x25: "Ukrainian", + 0x26: "Vietnamese", +}; diff --git a/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart b/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart index 2986aa7..78e5a4c 100644 --- a/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart +++ b/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart @@ -228,9 +228,10 @@ class WwiseHierarchyElement extends Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { var amhWu = project.amhWu; var imhWu = project.imhWu; - Map amhElements = {}; - Map imhElements = {}; - for (var chunk in project.hircChunksByType()) { + Map amhElements = {}; + Map imhElements = {}; + for (var chunkContext in project.hircChunksByType()) { + var chunk = chunkContext.value; var baseParams = chunk.getBaseParams(); var id = (chunk as BnkHircChunkBase).uid; var parent = baseParams.directParentID; @@ -241,7 +242,7 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { isAmh = true; } else if (chunk is BnkSound) { - element = WwiseSound(project: project, wuId: amhWu.id, chunk: chunk); + element = WwiseSound(project: project, wuId: amhWu.id, chunk: chunk, language: chunkContext.language); isAmh = true; } else if (chunk is BnkRandomSequence) { @@ -252,7 +253,7 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { var children = amhElements .values .where((e) => e.$2 == id) - .map((e) => e.$3) + .map((e) => e.$4) .toList(); element = WwiseSwitchContainer(project: project, wuId: amhWu.id, chunk: chunk, childElements: children); isAmh = true; @@ -279,9 +280,9 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { if (element != null) { if (isAmh) { - amhElements[id] = (id, parent, element); + amhElements[id] = (id, parent, chunkContext.name, element); } else { - imhElements[id] = (id, parent, element); + imhElements[id] = (id, parent, chunkContext.name, element); } } } @@ -291,16 +292,16 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { (imhWu, imhElements), ]; for (var (workUnit, elements) in hierarchies) { - for (var (id, parentId, element) in elements.values) { + for (var (id, parentId, bnkName, element) in elements.values) { var parent = elements[parentId]; if (parent == null && parentId != 0) { project.log(WwiseLogSeverity.warning, "Could not find parent $parentId for $id"); continue; } if (parent != null) - parent.$3.children.add(element); + parent.$4.addChild(element); else - workUnit.addChild(element, id); + workUnit.addWuChild(element, id, bnkName); } for (var child in workUnit.children) { child.oneTimeInit(); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart index 4017317..9d6d5d2 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart @@ -113,10 +113,10 @@ Future saveAttenuationsIntoWu(WwiseProjectGenerator project) async { var attenuationElement = WwiseAttenuation( wuId: project.attenuationsWu.id, project: project, - name: makeElementName(project, id: attenuation.uid, category: "Attenuation"), - attenuation: attenuation + name: makeElementName(project, id: attenuation.value.uid, category: "Attenuation"), + attenuation: attenuation.value ); - project.attenuationsWu.children.add(attenuationElement); + project.attenuationsWu.addChild(attenuationElement); } await project.attenuationsWu.save(); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseAudioFileSource.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseAudioFileSource.dart index f5ca1b8..d4918bb 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseAudioFileSource.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseAudioFileSource.dart @@ -3,7 +3,7 @@ import 'package:path/path.dart'; import 'package:xml/xml.dart'; import '../../utils.dart'; -import '../wwiseAudioFilesPrepare.dart'; +import '../bnkLoader.dart'; import '../wwiseElement.dart'; class WwiseAudioFileSource extends WwiseElement { @@ -19,7 +19,7 @@ class WwiseAudioFileSource extends WwiseElement { @override XmlElement toXml() { var xml = super.toXml(); - xml.children.add(makeXmlElement(name: "Language", text: audio.isVoice ? project.language : "SFX")); + xml.children.add(makeXmlElement(name: "Language", text: audio.language)); xml.children.add(makeXmlElement(name: "AudioFile", text: basename(audio.path))); return xml; } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseBus.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseBus.dart index 1fa5e95..f4c2160 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseBus.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseBus.dart @@ -9,13 +9,14 @@ Future saveBusesIntoWu(WwiseProjectGenerator project) async { Set usedBusIds = {}; usedBusIds.add(masterBusHash); for (var chunk in project.hircChunksByType()) { - var baseParams = chunk.getBaseParams(); + var baseParams = chunk.value.getBaseParams(); var busId = baseParams.overrideBusID; if (busId == 0) continue; usedBusIds.add(busId); } - for (var action in project.hircChunksByType()) { + for (var actionC in project.hircChunksByType()) { + var action = actionC.value; if (action.initialParams.isBus) usedBusIds.add(action.initialParams.idExt); var actionType = actionTypes[action.type]; @@ -49,13 +50,13 @@ Future saveBusesIntoWu(WwiseProjectGenerator project) async { name: wwiseIdToStr(busId), shortIdHint: busId, ); - masterAudioBus.children.add(bus); + masterAudioBus.addChild(bus); } else bus = project.defaultBus; project.buses[busId] = bus; } - masterAudioBus.children.sort((a, b) => a.name.compareTo(b.name)); - wu.children.addAll(defaultBuses); + masterAudioBus.sortChildren((a, b) => a.name.compareTo(b.name)); + wu.addAllChildren(defaultBuses); await wu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseConversionInfo.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseConversionInfo.dart index 5d894d1..c8d1f18 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseConversionInfo.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseConversionInfo.dart @@ -2,21 +2,20 @@ import 'package:xml/xml.dart'; import '../../utils.dart'; -import '../wwiseElement.dart'; import '../wwiseElementBase.dart'; import '../wwiseProjectGenerator.dart'; class WwiseConversionInfo extends WwiseElementBase { final String conversionId; - WwiseConversionInfo({required super.project, required this.conversionId}); + WwiseConversionInfo({required super.project, required this.conversionId}) : super(name: ""); WwiseConversionInfo.projectDefault(WwiseProjectGenerator project) : this(project: project, conversionId: project.defaultConversion.id); @override XmlElement toXml() { - var conversion = project.lookupElement(idV4: conversionId)! as WwiseElement; + var conversion = project.lookupElement(idV4: conversionId)!; return makeXmlElement(name: "ConversionInfo", children: [ makeXmlElement(name: "ConversionRef", attributes: { "Name": conversion.name, diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart index 72563f0..a9a9c6d 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart @@ -223,7 +223,8 @@ List _getRoomVerbProperties(BnkPluginData data) { } Future saveEffectsIntoWu(WwiseProjectGenerator project) async { - for (var effect in project.hircChunksByType()) { + for (var effectC in project.hircChunksByType()) { + var effect = effectC.value; if (_ignorePluginIds.contains(effect.fxId)) continue; var config = _pluginConfigs[effect.fxId]; @@ -233,7 +234,7 @@ Future saveEffectsIntoWu(WwiseProjectGenerator project) async { } var effectElement = WwiseEffect(wuId: project.effectsWu.id, project: project, effect: effect, config: config); if (effect.isShareSet) - project.effectsWu.addChild(effectElement, effect.uid); + project.effectsWu.addWuChild(effectElement, effect.uid, effectC.name); } await project.effectsWu.save(); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart index b25ef89..e3a2565 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart @@ -8,22 +8,27 @@ import 'wwiseAction.dart'; Future saveEventsHierarchy(WwiseProjectGenerator project) async { var actionsMap = { for (var action in project.hircChunksByType()) - action.uid: action + action.value.uid: action.value }; - for (var event in project.hircChunksByType()) { - project.eventsWu.addChild(WwiseElement( - wuId: project.eventsWu.id, - project: project, - tagName: "Event", - name: wwiseIdToStr(event.uid), - shortIdHint: event.uid, - children: [ - for (var actionId in event.ids) - if (project.options.actions && actionsMap.containsKey(actionId)) - WwiseAction(wuId: project.eventsWu.id, project: project, action: actionsMap[actionId]!) - ] - ), event.uid); + for (var eventC in project.hircChunksByType()) { + var event = eventC.value; + project.eventsWu.addWuChild( + WwiseElement( + wuId: project.eventsWu.id, + project: project, + tagName: "Event", + name: wwiseIdToStr(event.uid), + shortIdHint: event.uid, + children: [ + for (var actionId in event.ids) + if (project.options.actions && actionsMap.containsKey(actionId)) + WwiseAction(wuId: project.eventsWu.id, project: project, action: actionsMap[actionId]!) + ] + ), + event.uid, + eventC.name, + ); } for (var child in project.eventsWu.children) { child.oneTimeInit(); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseGameParameters.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseGameParameters.dart index 982955f..214b458 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseGameParameters.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseGameParameters.dart @@ -9,12 +9,14 @@ import '../wwiseUtils.dart'; Future saveGameParametersIntoWu(WwiseProjectGenerator project) async { Set usedGameParameterIds = {}; + Map parameterIdToBnk = {}; Map valueMinMax = {}; Map graphMinMax = {}; for (var chunk in project.hircChunksByType()) { - var baseParams = chunk.getBaseParams(); + var baseParams = chunk.value.getBaseParams(); for (var rtpc in baseParams.rtpc.rtpc) { usedGameParameterIds.add(rtpc.rtpcId); + parameterIdToBnk.putIfAbsent(rtpc.rtpcId, () => chunk.name); var allX = rtpc.rtpcGraphPoint.map((e) => e.x).toList(); var minX = allX.reduce((value, element) => value < element ? value : element); var maxX = allX.reduce((value, element) => value > element ? value : element); @@ -22,11 +24,13 @@ Future saveGameParametersIntoWu(WwiseProjectGenerator project) async { graphMinMax[rtpc.rtpcId] = (min: min(minX, minMax.min), max: max(maxX, minMax.max)); } } - for (var action in project.hircChunksByType()) { + for (var actionC in project.hircChunksByType()) { + var action = actionC.value; var specificParams = action.specificParams; if (specificParams is BnkValueActionParams) { if (gameParamActionTypes.contains(action.ulActionType & 0xFF00)) { usedGameParameterIds.add(action.initialParams.idExt); + parameterIdToBnk.putIfAbsent(action.initialParams.idExt, () => actionC.name); var subParams = specificParams.specificParams; if (subParams is BnkGameParameterParams) { var value = subParams.base; @@ -73,6 +77,6 @@ Future saveGameParametersIntoWu(WwiseProjectGenerator project) async { ..sort((a, b) => a.$2.name.compareTo(b.$2.name)); for (var (id, param) in gameParameters) - project.gameParametersWu.addChild(param, id); + project.gameParametersWu.addWuChild(param, id, parameterIdToBnk[id]!); await project.gameParametersWu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseMusicSwitch.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseMusicSwitch.dart index d8cc876..3d26288 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseMusicSwitch.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseMusicSwitch.dart @@ -2,7 +2,6 @@ import '../../../fileTypeUtils/audio/bnkIO.dart'; import '../../../fileTypeUtils/audio/wemIdsToNames.dart'; import '../../utils.dart'; -import '../wwiseElement.dart'; import '../wwiseProperty.dart'; import '../wwiseUtils.dart'; import 'hierarchyBaseElements.dart'; @@ -42,7 +41,7 @@ class WwiseMusicSwitch extends WwiseHierarchyElement { if (chunk.pAssocs.isNotEmpty) makeXmlElement(name: "GroupingList", children: chunk.pAssocs.map((sw) { var groupChild = group?.children.where((c) => c.id == sw.switchID).firstOrNull; - var child = project.lookupElement(idFnv: sw.nodeID) as WwiseElement?; + var child = project.lookupElement(idFnv: sw.nodeID); return makeXmlElement(name: "Grouping", children: [ makeXmlElement(name: "SwitchRef", attributes: {"Name": groupChild?.name ?? "", "ID": groupChild?.uuid ?? wwiseNullId}), makeXmlElement(name: "ChildRef", attributes: {"Name": child?.name ?? "", "ID": child?.id ?? wwiseNullId}), diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart index b1eb541..203ec62 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart @@ -12,7 +12,7 @@ import 'wwiseSourcePlugin.dart'; const _loopPropId = 0x07; class WwiseSound extends WwiseHierarchyElement { - WwiseSound({required super.wuId, required super.project, required super.chunk}) : super( + WwiseSound({required super.wuId, required super.project, required super.chunk, required String language}) : super( tagName: "Sound", name: makeElementName(project, id: chunk.uid, category: "Sound", name: wemIdsToNames[chunk.uid] ?? wemIdsToNames[chunk.bankData.mediaInformation.sourceID], parentId: chunk.baseParams.directParentID), shortId: chunk.uid, @@ -25,7 +25,7 @@ class WwiseSound extends WwiseHierarchyElement { ], children: [ if (chunk.bankData.mediaInformation.uFileID == 0 && WwiseSourcePlugin.isSourcePlugin(project, chunk.bankData.mediaInformation.sourceID)) - WwiseSourcePlugin(wuId: wuId, project: project, fxId: chunk.bankData.mediaInformation.sourceID, isVoice: chunk.bankData.mediaInformation.uSourceBits & 1 != 0) + WwiseSourcePlugin(wuId: wuId, project: project, fxId: chunk.bankData.mediaInformation.sourceID, language: language) else if (project.soundFiles.containsKey(chunk.bankData.mediaInformation.sourceID)) WwiseAudioFileSource(wuId: wuId, project: project, audio: project.soundFiles[chunk.bankData.mediaInformation.sourceID]!), ], diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseSoundBanks.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseSoundBanks.dart index 6737b2b..1960e3d 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseSoundBanks.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseSoundBanks.dart @@ -1,44 +1,63 @@ import '../../utils.dart'; import '../wwiseElement.dart'; +import '../wwiseElementBase.dart'; import '../wwiseProjectGenerator.dart'; +import 'wwiseWorkUnit.dart'; Future makeWwiseSoundBank(WwiseProjectGenerator project) async { project.soundBanksWu.defaultChildren.clear(); - project.soundBanksWu.children.add( - WwiseElement( + var wuChildren = [project.amhWu, project.imhWu, project.eventsWu]; + void markBnkUsage(WwiseElementBase obj) { + for (var child in obj.children) + markBnkUsage(child); + var childBnkNames = obj.children + .map((e) => e.bnkName) + .whereType() + .toSet(); + if (childBnkNames.length != 1) + return; + if (obj.bnkName != null && obj.bnkName != childBnkNames.first) + return; + obj.bnkName = childBnkNames.first; + } + for (var obj in wuChildren) + markBnkUsage(obj); + + for (var bnkName in project.bnkNames) { + List topLevelObjects = []; + void findTopLevelObjects(WwiseElementBase obj) { + if (obj.bnkName == bnkName) { + topLevelObjects.add(obj); + return; + } + for (var child in obj.children) + findTopLevelObjects(child); + } + for (var obj in wuChildren) + findTopLevelObjects(obj); + + + project.soundBanksWu.addChild(WwiseElement( wuId: project.soundBanksWu.id, project: project, tagName: "SoundBank", - name: project.projectName, + name: bnkName, additionalChildren: [ makeXmlElement(name: "ObjectInclusionList", children: [ - makeXmlElement(name: "ObjectRef", attributes: { - "Name": project.amhWu.name, - "ID": project.amhWu.id, - "WorkUnitID": project.amhWu.workUnitId, - "Filter": "7", - "Origin": "Manual", - }), - makeXmlElement(name: "ObjectRef", attributes: { - "Name": project.imhWu.name, - "ID": project.imhWu.id, - "WorkUnitID": project.imhWu.workUnitId, - "Filter": "7", - "Origin": "Manual", - }), - makeXmlElement(name: "ObjectRef", attributes: { - "Name": project.eventsWu.name, - "ID": project.eventsWu.id, - "WorkUnitID": project.eventsWu.workUnitId, - "Filter": "7", - "Origin": "Manual", - }), + for (var element in topLevelObjects) + makeXmlElement(name: "ObjectRef", attributes: { + "Name": element.name, + "ID": element.id, + "WorkUnitID": element is WwiseElement ? element.wuId : (element as WwiseWorkUnit).workUnitId, + "Filter": "7", + "Origin": "Manual", + }), ]), makeXmlElement(name: "ObjectExclusionList"), makeXmlElement(name: "GameSyncExclusionList"), ] - ) - ); + )); + } await project.soundBanksWu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseSourcePlugin.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseSourcePlugin.dart index 42edec1..22fa391 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseSourcePlugin.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseSourcePlugin.dart @@ -12,7 +12,7 @@ const Map _pluginConfigs = { }; class WwiseSourcePlugin extends WwiseElement { - WwiseSourcePlugin._({required super.wuId, required super.project, required super.shortId, required _PluginConfig config, required BnkPluginData pluginData, required bool isVoice}) : super( + WwiseSourcePlugin._({required super.wuId, required super.project, required super.shortId, required _PluginConfig config, required BnkPluginData pluginData, required String language}) : super( tagName: "SourcePlugin", name: config.name, properties: config.handler(pluginData), @@ -21,10 +21,10 @@ class WwiseSourcePlugin extends WwiseElement { "CompanyID": "0", "PluginID": config.pluginId, }, - additionalChildren: [makeXmlElement(name: "Language", text: isVoice ? project.language : "SFX")], + additionalChildren: [makeXmlElement(name: "Language", text: language)], ); - factory WwiseSourcePlugin({required String wuId, required WwiseProjectGenerator project, required int fxId, required bool isVoice}) { + factory WwiseSourcePlugin({required String wuId, required WwiseProjectGenerator project, required int fxId, required String language}) { var chunk = project.hircChunkById(fxId)!; var config = _pluginConfigs[chunk.fxId]!; return WwiseSourcePlugin._( @@ -33,7 +33,7 @@ class WwiseSourcePlugin extends WwiseElement { shortId: fxId, config: config, pluginData: chunk.pluginData, - isVoice: isVoice, + language: language, ); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart index 0e6f50c..39ab890 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart @@ -43,7 +43,7 @@ XmlElement? makeWwiseStatInfo(WwiseProjectGenerator project, List(state.ulStateInstanceID)!; customStates.add( makeXmlElement(name: "CustomState", children: [ makeXmlElement(name: "StateRef", attributes: {"Name": stateRef.name, "ID": stateRef.uuid}), diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseStates.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseStates.dart index abea8b2..defb9cc 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseStates.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseStates.dart @@ -8,18 +8,21 @@ import 'wwiseSwitchOrState.dart'; Future saveStatesIntoWu(WwiseProjectGenerator project) async { Map> usedStateGroupIds = {}; + Map groupIdToBnk = {}; for (var baseChunk in project.hircChunksByType()) { - var baseParams = baseChunk.getBaseParams(); + var baseParams = baseChunk.value.getBaseParams(); for (var stateGroup in baseParams.states.stateGroup) { for (var state in stateGroup.state) { addWwiseGroupUsage(usedStateGroupIds, stateGroup.ulStateGroupID, state.ulStateID); + groupIdToBnk.putIfAbsent(stateGroup.ulStateGroupID, () => baseChunk.name); } } } for (var action in project.hircChunksByType()) { - var actionParams = action.specificParams; + var actionParams = action.value.specificParams; if (actionParams is BnkStateActionParams) { addWwiseGroupUsage(usedStateGroupIds, actionParams.ulStateGroupID, actionParams.ulTargetStateID); + groupIdToBnk.putIfAbsent(actionParams.ulStateGroupID, () => action.name); } } @@ -66,6 +69,6 @@ Future saveStatesIntoWu(WwiseProjectGenerator project) async { ..sort((a, b) => a.$2.name.compareTo(b.$2.name)); for (var (id, group) in stateGroups) - project.statesWu.addChild(group, id); + project.statesWu.addWuChild(group, id, groupIdToBnk[id]!); await project.statesWu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseSwitches.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseSwitches.dart index 21ca7ed..78655f9 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseSwitches.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseSwitches.dart @@ -7,22 +7,28 @@ import 'wwiseSwitchOrState.dart'; Future saveSwitchesIntoWu(WwiseProjectGenerator project) async { Map> usedSwitchGroupIds = {}; - for (var musicSwitch in project.hircChunksByType()) { + Map groupIdToBnk = {}; + for (var musicSwitchC in project.hircChunksByType()) { + var musicSwitch = musicSwitchC.value; addWwiseGroupUsage(usedSwitchGroupIds, musicSwitch.ulGroupID, musicSwitch.ulDefaultSwitch); + groupIdToBnk.putIfAbsent(musicSwitch.ulGroupID, () => musicSwitchC.name); for (var switchAssoc in musicSwitch.pAssocs) { addWwiseGroupUsage(usedSwitchGroupIds, musicSwitch.ulGroupID, switchAssoc.switchID); } } - for (var musicSwitch in project.hircChunksByType()) { + for (var musicSwitchC in project.hircChunksByType()) { + var musicSwitch = musicSwitchC.value; addWwiseGroupUsage(usedSwitchGroupIds, musicSwitch.ulGroupID, musicSwitch.ulDefaultSwitch); + groupIdToBnk.putIfAbsent(musicSwitch.ulGroupID, () => musicSwitchC.name); for (var switchAssoc in musicSwitch.switches) { addWwiseGroupUsage(usedSwitchGroupIds, musicSwitch.ulGroupID, switchAssoc.ulSwitchID); } } for (var action in project.hircChunksByType()) { - var params = action.specificParams; + var params = action.value.specificParams; if (params is BnkSwitchActionParams) { addWwiseGroupUsage(usedSwitchGroupIds, params.ulSwitchGroupID, params.ulSwitchStateID); + groupIdToBnk.putIfAbsent(params.ulSwitchGroupID, () => action.name); } } @@ -67,6 +73,6 @@ Future saveSwitchesIntoWu(WwiseProjectGenerator project) async { ..sort((a, b) => a.$2.name.compareTo(b.$2.name)); for (var (id, group) in switchGroups) - project.switchesWu.addChild(group, id); + project.switchesWu.addWuChild(group, id, groupIdToBnk[id]!); await project.switchesWu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseTransitionList.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseTransitionList.dart index d2cad88..0ee7a15 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseTransitionList.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseTransitionList.dart @@ -11,10 +11,10 @@ import '../wwiseUtils.dart'; XmlElement makeWwiseTransitionList(WwiseProjectGenerator project, String wuId, List transitions) { return makeXmlElement(name: "TransitionList", children: transitions .map((transition) { - var src = project.lookupElement(idFnv: transition.srcID) as WwiseElement?; - var dest = project.lookupElement(idFnv: transition.dstID) as WwiseElement?; - var destPlItem = project.lookupElement(idFnv: transition.dstRule.uJumpToID) as WwiseElement?; - var transSegment = project.lookupElement(idFnv: transition.musicTransition.segmentID) as WwiseElement?; + var src = project.lookupElement(idFnv: transition.srcID); + var dest = project.lookupElement(idFnv: transition.dstID); + var destPlItem = project.lookupElement(idFnv: transition.dstRule.uJumpToID); + var transSegment = project.lookupElement(idFnv: transition.musicTransition.segmentID); if (transition.srcID > 0 && src == null) { project.log(WwiseLogSeverity.warning, "Transition source not found: ${wwiseIdToStr(transition.srcID)}"); return null; @@ -27,8 +27,8 @@ XmlElement makeWwiseTransitionList(WwiseProjectGenerator project, String wuId, L project.log(WwiseLogSeverity.warning, "Transition destination playlist item not found: ${wwiseIdToStr(transition.dstRule.uJumpToID)}"); return null; } - String? srcCustomCueName = transition.srcRule.uMarkerID != 0 ? (project.lookupElement(idFnv: transition.srcRule.uMarkerID) as WwiseElement?)?.name : null; - String? destCustomCueName = transition.dstRule.uMarkerID != 0 ? (project.lookupElement(idFnv: transition.dstRule.uMarkerID) as WwiseElement?)?.name : null; + String? srcCustomCueName = transition.srcRule.uMarkerID != 0 ? (project.lookupElement(idFnv: transition.srcRule.uMarkerID))?.name : null; + String? destCustomCueName = transition.dstRule.uMarkerID != 0 ? (project.lookupElement(idFnv: transition.dstRule.uMarkerID))?.name : null; return makeXmlElement(name: "Transition", children: [ makeXmlElement(name: "Source", children: [ if (transition.srcID == -1) diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart index e3405ef..8a9d151 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart @@ -10,7 +10,9 @@ import '../wwiseUtils.dart'; Future saveTriggersIntoWu(WwiseProjectGenerator project) async { Set usedTriggerIds = {}; - for (var chunk in project.hircChunks) { + Map triggerIdToBnk = {}; + for (var chunkC in project.hircChunks) { + var chunk = chunkC.value; BnkMusicNodeParams? musicParams; if (chunk is BnkMusicSegment) musicParams = chunk.musicParams; @@ -19,11 +21,14 @@ Future saveTriggersIntoWu(WwiseProjectGenerator project) async { else if (chunk is BnkMusicSwitch) musicParams = chunk.musicTransParams.musicParams; if (musicParams != null) { - for (var stinger in musicParams.stingers) + for (var stinger in musicParams.stingers) { usedTriggerIds.add(stinger.triggerID); + triggerIdToBnk.putIfAbsent(stinger.triggerID, () => chunkC.name); + } } if (chunk is BnkAction && chunk.type & 0xFF00 == 0x1D00) { usedTriggerIds.add(chunk.initialParams.idExt); + triggerIdToBnk.putIfAbsent(chunk.initialParams.idExt, () => chunkC.name); } } @@ -42,7 +47,7 @@ Future saveTriggersIntoWu(WwiseProjectGenerator project) async { ..sort((a, b) => a.$2.name.compareTo(b.$2.name)); for (var (id, param) in triggers) - project.triggersWu.addChild(param, id); + project.triggersWu.addWuChild(param, id, triggerIdToBnk[id]!); await project.triggersWu.save(); } diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseWorkUnit.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseWorkUnit.dart index c4f38e0..e5a807b 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseWorkUnit.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseWorkUnit.dart @@ -8,11 +8,11 @@ import '../../utils.dart'; import '../wwiseElement.dart'; import '../wwiseElementBase.dart'; import '../wwiseProjectGenerator.dart'; +import '../wwiseUtils.dart'; class WwiseWorkUnit extends WwiseElementBase { final String path; final String tagName; - final String name; final String workUnitId; final List defaultChildren; final String _folder; @@ -22,7 +22,7 @@ class WwiseWorkUnit extends WwiseElementBase { required super.project, required this.path, required this.tagName, - required this.name, + required super.name, String? workUnitId, List? defaultChildren, super. children, @@ -52,21 +52,24 @@ class WwiseWorkUnit extends WwiseElementBase { ); } - void addChild(WwiseElement child, int id) { - Iterable? folders = project.bnkFolders[id]; + void addWuChild(WwiseElement child, int id, String bnkName) { + child.bnkName = bnkName; + Iterable? folders = getObjectFolder(bnkName, id); if (folders == null || folders.isEmpty) { - children.add(child); + addChild(child); + project.bnkTopLevelUuids.putIfAbsent(bnkName, () => {}).add(child.id); return; } if (folders.first == name || folders.first == _folder) { folders = folders.skip(1); } - _addChild(child, folders, this); + _addWuChild(child, folders, this, bnkName); } - void _addChild(WwiseElement child, Iterable folders, WwiseElementBase parent) { + void _addWuChild(WwiseElement child, Iterable folders, WwiseElementBase parent, String bnkName) { if (folders.length <= 1) { - parent.children.add(child); + parent.addChild(child); + project.bnkTopLevelUuids.putIfAbsent(bnkName, () => {}).add(child.id); return; } var folderName = folders.first; @@ -76,9 +79,9 @@ class WwiseWorkUnit extends WwiseElementBase { .firstOrNull; if (folder == null) { folder = _WwiseFolder(project: project, wuId: id, name: folderName); - parent.children.add(folder); + parent.addChild(folder); } - _addChild(child, folders.skip(1), folder); + _addWuChild(child, folders.skip(1), folder, bnkName); } @override diff --git a/lib/utils/wwiseProjectGenerator/wwiseAudioFilesPrepare.dart b/lib/utils/wwiseProjectGenerator/wwiseAudioFilesPrepare.dart deleted file mode 100644 index 06206e8..0000000 --- a/lib/utils/wwiseProjectGenerator/wwiseAudioFilesPrepare.dart +++ /dev/null @@ -1,143 +0,0 @@ - -import 'dart:io'; -import 'dart:math'; - -import 'package:path/path.dart'; - -import '../../background/wemFilesIndexer.dart'; -import '../../fileTypeUtils/audio/bnkIO.dart'; -import '../../fileTypeUtils/audio/wemToWavConverter.dart'; -import '../utils.dart'; -import 'wwiseIdGenerator.dart'; -import 'wwiseProjectGenerator.dart'; -import 'wwiseUtils.dart'; - -class WwiseAudioFile { - final int id; - final int? prefetchId; - final String name; - final String path; - final bool isVoice; - int _idsGenerated = 0; - - WwiseAudioFile(this.id, this.prefetchId, this.name, this.path, this.isVoice); - - int nextWemId(WwiseIdGenerator idGen) { - if (_idsGenerated == 0) { - _idsGenerated++; - return id; - } - if (_idsGenerated == 1) { - _idsGenerated++; - if (prefetchId != null && prefetchId! > id) - return prefetchId!; - } - return idGen.wemId(min: id + 1); - } -} - -Future> prepareWwiseAudioFiles(WwiseProjectGenerator project) async { - Map soundFiles = {}; - - var bnk = project.bnk; - var didx = bnk.chunks.whereType().firstOrNull; - var data = bnk.chunks.whereType().firstOrNull; - if ((didx == null) != (data == null)) { - project.log(WwiseLogSeverity.error, "BNK file has only one of DIDX and DATA chunks"); - throw Exception("BNK file has only one of DIDX and DATA chunks"); - } - - var internalWemFiles = { - for (var (i, info) in (didx?.files ?? []).indexed) - info.id: data?.wemFiles[i] - }; - Set usedWemIds = internalWemFiles.keys.toSet(); - Set languageWemIds = {}; - Map srcToFileIds = {}; - _collectWemIdStats(project, srcToFileIds, usedWemIds, languageWemIds); - - var sfxDir = join(project.projectPath, "Originals", "SFX"); - var langDir = project.language != "SFX" ? join(project.projectPath, "Originals", "Voices", project.language) : sfxDir; - await Directory(sfxDir).create(recursive: true); - await Directory(langDir).create(recursive: true); - Future? tmpDirLazy; - int processed = 0; - try { - await futuresWaitBatched(usedWemIds.map((id) async { - project.status.currentMsg.value = "Processing WEM files: $processed / ${usedWemIds.length}"; - processed++; - var srcId = id; - var fileId = srcToFileIds[srcId]; - var trueId = srcId; - var isLangFile = languageWemIds.contains(srcId) || (fileId != null && languageWemIds.contains(fileId)); - var wavPath = join(isLangFile ? langDir : sfxDir, "${wwiseIdToStr(id)}.wav"); - int? prefetchId; - String? wemPath; - if (fileId != null) { - wemPath = wemFilesLookup.lookup[fileId]; - if (wemPath == null) - project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(fileId, alwaysIncludeId: true)} is prefetched, but original file is not indexed"); - else { - trueId = fileId; - prefetchId = srcId; - } - } - wemPath ??= wemFilesLookup.lookup[srcId]; - if (wemPath == null) { - var file = internalWemFiles[srcId]; - if (file == null) { - project.log(WwiseLogSeverity.warning, "WEM file ${wwiseIdToStr(srcId, alwaysIncludeId: true)} is not indexed or found in BNK file"); - return; - } - tmpDirLazy ??= Directory.systemTemp.createTemp("wwise_audio_files"); - var tmpDir = await tmpDirLazy!; - wemPath = join(tmpDir.path, "$id.wem"); - await File(wemPath).writeAsBytes(file); - } - if (!await File(wavPath).exists()) - await wemToWav(wemPath, wavPath); - var audioFile = WwiseAudioFile(trueId, prefetchId, wwiseIdToStr(id), wavPath, isLangFile); - if (trueId != prefetchId && prefetchId != null) { - soundFiles[trueId] = audioFile; - soundFiles[prefetchId] = audioFile; - } else if (!soundFiles.containsKey(id)) { - soundFiles[id] = audioFile; - } - }), max(2, Platform.numberOfProcessors ~/ 2)); - } finally { - await (await tmpDirLazy)?.delete(recursive: true); - } - return soundFiles; -} - -void _collectWemIdStats(WwiseProjectGenerator project, Map srcToFileId, Set usedWemIds, Set languageWemIds) { - for (var sound in project.hircChunksByType()) { - var info = sound.bankData.mediaInformation; - if (info.uFileID == 0) - continue; - usedWemIds.add(info.sourceID); - var isVoice = sound.bankData.mediaInformation.uSourceBits & 1 != 0; - if (isVoice) - languageWemIds.add(info.sourceID); - if (sound.bankData.streamType == 2) { - usedWemIds.add(info.uFileID); - srcToFileId[info.sourceID] = info.uFileID; - if (isVoice) - languageWemIds.add(info.uFileID); - } - } - for (var track in project.hircChunksByType()) { - for (var source in track.sources) { - var isVoice = source.uSourceBits & 1 != 0; - usedWemIds.add(source.sourceID); - if (isVoice) - languageWemIds.add(source.sourceID); - if (source.streamType == 2) { - usedWemIds.add(source.fileID); - srcToFileId[source.sourceID] = source.fileID; - if (isVoice) - languageWemIds.add(source.fileID); - } - } - } -} diff --git a/lib/utils/wwiseProjectGenerator/wwiseElement.dart b/lib/utils/wwiseProjectGenerator/wwiseElement.dart index 54c269f..f7dcae5 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseElement.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseElement.dart @@ -10,7 +10,6 @@ import 'wwiseProperty.dart'; class WwiseElement extends WwiseElementBase { final String wuId; final String tagName; - final String name; final int? shortId; final WwisePropertyList properties; final Map additionalAttributes; @@ -21,7 +20,7 @@ class WwiseElement extends WwiseElementBase { required this.wuId, required super.project, required this.tagName, - required this.name, + required super.name, super.id, this.shortId, int? shortIdHint, diff --git a/lib/utils/wwiseProjectGenerator/wwiseElementBase.dart b/lib/utils/wwiseProjectGenerator/wwiseElementBase.dart index 8385ab5..19a6e56 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseElementBase.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseElementBase.dart @@ -9,12 +9,15 @@ import 'wwiseProjectGenerator.dart'; abstract class WwiseElementBase { final String id; + final String name; final WwiseProjectGenerator project; - final List children; + final List _children; + Iterable get children => _children; + String? bnkName; - WwiseElementBase({required this.project, String? id, List? children}) + WwiseElementBase({required this.project, required this.name, String? id, Iterable? children}) : id = id ?? project.idGen.uuid(), - children = children ?? [] { + _children = children is List ? children : (children?.toList() ?? []) { project.putElement(this); } @@ -27,4 +30,18 @@ abstract class WwiseElementBase { ]); await File(path).writeAsString(doc.toPrettyString()); } + + void addChild(WwiseElement child) { + _children.add(child); + } + + addAllChildren(Iterable children) { + for (var child in children) { + addChild(child); + } + } + + sortChildren([int Function(WwiseElement a, WwiseElement b)? compare]) { + _children.sort(compare); + } } diff --git a/lib/utils/wwiseProjectGenerator/wwiseIdGenerator.dart b/lib/utils/wwiseProjectGenerator/wwiseIdGenerator.dart index 1412dbe..7f151d1 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseIdGenerator.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseIdGenerator.dart @@ -29,7 +29,7 @@ class WwiseIdGenerator { _usedIds.add(fnvHash(bus.name)); } for (var chunk in project.hircChunks) - _usedIds.add(chunk.uid); + _usedIds.add(chunk.value.uid); } void markIdUsed(int id) { diff --git a/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart b/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart index 23279e7..bbc99df 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart @@ -10,9 +10,6 @@ import 'package:xml/xml.dart'; import '../../fileTypeUtils/audio/bnkIO.dart'; import '../../fileTypeUtils/audio/bnkNotes.dart'; -import '../../fileTypeUtils/audio/wemIdsToNames.dart'; -import '../../fileTypeUtils/audio/wwiseObjectPath.dart'; -import '../../fileTypeUtils/utils/ByteDataWrapper.dart'; import '../../fileTypeUtils/xml/xmlExtension.dart'; import '../../fileTypeUtils/yax/japToEng.dart'; import '../../stateManagement/events/statusInfo.dart'; @@ -29,7 +26,7 @@ import 'elements/wwiseSwitchOrState.dart'; import 'elements/wwiseSwitches.dart'; import 'elements/wwiseTriggers.dart'; import 'elements/wwiseWorkUnit.dart'; -import 'wwiseAudioFilesPrepare.dart'; +import 'bnkLoader.dart'; import 'wwiseElement.dart'; import 'wwiseElementBase.dart'; import 'wwiseIdGenerator.dart'; @@ -77,11 +74,12 @@ class WwiseProjectGeneratorStatus { class WwiseProjectGenerator { final String projectName; final String projectPath; - final BnkFile bnk; + final List bnkPaths; + final List bnkNames = []; final WwiseProjectGeneratorOptions options; final WwiseProjectGeneratorStatus status; - late final Map> bnkFolders; - final List _hircChunks; + final List> _hircChunks = []; + final Map> _hircChunksById = {}; final Map _elements = {}; final Map _shortToFullId = {}; final WwiseIdGenerator idGen; @@ -91,7 +89,7 @@ class WwiseProjectGenerator { final Map switchGroups = {}; final Map gameParameters = {}; final Map buses = {}; - late final Map bnkStateChunks = {}; + final Map> bnkTopLevelUuids = {}; late final WwiseElement defaultConversion; late final WwiseElement defaultBus; late final WwiseWorkUnit attenuationsWu; @@ -105,30 +103,14 @@ class WwiseProjectGenerator { late final WwiseWorkUnit statesWu; late final WwiseWorkUnit switchesWu; late final WwiseWorkUnit triggersWu; - late final String language; - WwiseProjectGenerator(this.projectName, this.projectPath, this.bnk, this._hircChunks, this.options, this.status) - : idGen = WwiseIdGenerator(projectName) { - var header = bnk.chunks.whereType().first; - language = _languageIds[header.languageId] ?? "SFX"; - var bnkId = header.bnkId; - var bnkPaths = wwiseBnkToIdObjectPath[projectName] ?? wwiseBnkToIdObjectPath[wemIdsToNames[bnkId]] ?? wwiseIdToObjectPath; - Map joinedBnkPaths = {}; - joinedBnkPaths.addAll(wwiseBnkToIdObjectPath["Init"]!); - joinedBnkPaths.addAll(bnkPaths); - bnkFolders = { - for (var entry in joinedBnkPaths.entries) - entry.key: entry.value.split("/").where((e) => e.isNotEmpty).toList(), - }; - for (var state in hircChunksByType()) - bnkStateChunks[state.uid] = state; - } + WwiseProjectGenerator(this.projectName, this.projectPath, this.bnkPaths, this.options, this.status) + : idGen = WwiseIdGenerator(projectName); - static Future generateFromBnk(String bnkPath, String savePath, WwiseProjectGeneratorOptions options, WwiseProjectGeneratorStatus status) async { + static Future generateFromBnks(String projectName, List bnkPaths, String savePath, WwiseProjectGeneratorOptions options, WwiseProjectGeneratorStatus status) async { try { isLoadingStatus.pushIsLoading(); status.logs.add(WwiseLog(WwiseLogSeverity.info, "Starting to generate Wwise project...")); - var projectName = basenameWithoutExtension(bnkPath); var projectPath = join(savePath, projectName); // clean if (await Directory(projectPath).exists()) { @@ -156,16 +138,8 @@ class WwiseProjectGenerator { var wprojData = await File(wprojPathNew).readAsString(); wprojData = wprojData.replaceAll("test_project", projectName); await File(wprojPathNew).writeAsString(wprojData); - // read bnk - var bnk = BnkFile.read(await ByteDataWrapper.fromFile(bnkPath)); - var hirc = bnk.chunks.whereType().firstOrNull; - if (hirc == null) { - status.logs.add(WwiseLog(WwiseLogSeverity.error, ("Error: BNK file has no HIRC chunk"))); - return null; - } - var hircChunks = hirc.chunks; // generate - var generator = WwiseProjectGenerator(projectName, projectPath, bnk, hircChunks, options, status); + var generator = WwiseProjectGenerator(projectName, projectPath, bnkPaths, options, status); unawaited(generator.run() .catchError((e, st) { messageLog.add("$e\n$st"); @@ -182,11 +156,14 @@ class WwiseProjectGenerator { Future run() async { status.currentMsg.value = "Generating project..."; + await loadBnks(this, bnkPaths, bnkNames, _hircChunks, _hircChunksById, soundFiles); + var unknownChunks = hircChunksByType().toList(); if (unknownChunks.isNotEmpty) { - var chunkTypes = unknownChunks.map((c) => c.type).toSet(); + var chunkTypes = unknownChunks.map((c) => c.value.type).toSet(); log(WwiseLogSeverity.warning, "${unknownChunks.length} unknown chunks: ${chunkTypes.join(", ")}"); } + await Future.wait([ WwiseWorkUnit.emptyFromXml(this, join(projectPath, "Attenuations", _defaultWorkUnit)).then((wu) => attenuationsWu = wu), WwiseWorkUnit.emptyFromXml(this, join(projectPath, "Actor-Mixer Hierarchy", _defaultWorkUnit)).then((wu) => amhWu = wu), @@ -200,6 +177,7 @@ class WwiseProjectGenerator { WwiseWorkUnit.emptyFromXml(this, join(projectPath, "Switches", _defaultWorkUnit)).then((wu) => switchesWu = wu), WwiseWorkUnit.emptyFromXml(this, join(projectPath, "Triggers", _defaultWorkUnit)).then((wu) => triggersWu = wu), ]); + var wprojPath = join(projectPath, "$projectName.wproj"); var wprojDoc = XmlDocument.parse(await File(wprojPath).readAsString()); var defaultConversionElement = wprojDoc.rootElement.findAllElements("DefaultConversion").first; @@ -220,9 +198,6 @@ class WwiseProjectGenerator { saveBusesIntoWu(this), if (options.seekTable) _enableSeekTable(), - if (options.wems) - prepareWwiseAudioFiles(this).then((files) => soundFiles.addAll(files)), - makeWwiseSoundBank(this), ]); idGen.init(this); @@ -232,15 +207,16 @@ class WwiseProjectGenerator { await saveHierarchyBaseElements(this); if (options.events) await saveEventsHierarchy(this); + await makeWwiseSoundBank(this); log(WwiseLogSeverity.info, "Project generated successfully"); status.currentMsg.value = ""; status.isDone.value = true; } - Iterable get hircChunks => _hircChunks; - Iterable hircChunksByType() => _hircChunks.whereType(); - T? hircChunkById(int id) => _hircChunks.where((e) => e.uid == id).firstOrNull as T?; + Iterable> get hircChunks => _hircChunks; + Iterable> hircChunksByType() => _hircChunks.where((c) => c.value is T).map((c) => c.cast()); + T? hircChunkById(int id) => _hircChunksById[id]?.value as T?; WwiseElementBase? lookupElement({String? idV4, int? idFnv}) { if (idV4 != null) { @@ -325,45 +301,3 @@ class WwiseLog { } const _defaultWorkUnit = "Default Work Unit.wwu"; - -const _languageIds = { - 0x00: "SFX", - 0x01: "Arabic", - 0x02: "Bulgarian", - 0x03: "Chinese(HK)", - 0x04: "Chinese(PRC)", - 0x05: "Chinese(Taiwan)", - 0x06: "Czech", - 0x07: "Danish", - 0x08: "Dutch", - 0x09: "English(Australia)", - 0x0A: "English(India)", - 0x0B: "English(UK)", - 0x0C: "English(US)", - 0x0D: "Finnish", - 0x0E: "French(Canada)", - 0x0F: "French(France)", - 0x10: "German", - 0x11: "Greek", - 0x12: "Hebrew", - 0x13: "Hungarian", - 0x14: "Indonesian", - 0x15: "Italian", - 0x16: "Japanese", - 0x17: "Korean", - 0x18: "Latin", - 0x19: "Norwegian", - 0x1A: "Polish", - 0x1B: "Portuguese(Brazil)", - 0x1C: "Portuguese(Portugal)", - 0x1D: "Romanian", - 0x1E: "Russian", - 0x1F: "Slovenian", - 0x20: "Spanish(Mexico)", - 0x21: "Spanish(Spain)", - 0x22: "Spanish(US)", - 0x23: "Swedish", - 0x24: "Turkish", - 0x25: "Ukrainian", - 0x26: "Vietnamese", -}; diff --git a/lib/utils/wwiseProjectGenerator/wwiseUtils.dart b/lib/utils/wwiseProjectGenerator/wwiseUtils.dart index d15b13b..ac800cf 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseUtils.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseUtils.dart @@ -1,6 +1,7 @@ import '../../fileTypeUtils/audio/bnkIO.dart'; import '../../fileTypeUtils/audio/wemIdsToNames.dart'; +import '../../fileTypeUtils/audio/wwiseObjectPath.dart'; import 'elements/hierarchyBaseElements.dart'; import 'wwiseProjectGenerator.dart'; @@ -142,3 +143,11 @@ String makeElementName(WwiseProjectGenerator project, {required int id, required name += " ($id)"; return project.makeName(name, parentId); } + +List? getObjectFolder(String bnkName, int id) { + String? path; + path ??= wwiseBnkToIdObjectPath[bnkName]?[id]; + path ??= wwiseBnkToIdObjectPath["Init"]?[id]; + path ??= wwiseIdToObjectPath[id]; + return path?.split("/").where((e) => e.isNotEmpty).toList(); +} diff --git a/lib/widgets/misc/wwiseProjectGeneratorPopup.dart b/lib/widgets/misc/wwiseProjectGeneratorPopup.dart index c305df5..74fce40 100644 --- a/lib/widgets/misc/wwiseProjectGeneratorPopup.dart +++ b/lib/widgets/misc/wwiseProjectGeneratorPopup.dart @@ -10,9 +10,9 @@ import '../../stateManagement/Property.dart'; import '../../stateManagement/preferencesData.dart'; import '../../utils/utils.dart'; import '../../utils/wwiseProjectGenerator/wwiseProjectGenerator.dart'; +import '../propEditors/UnderlinePropTextField.dart'; import '../propEditors/boolPropCheckbox.dart'; import '../propEditors/primaryPropTextField.dart'; -import '../propEditors/propTextField.dart'; import '../theme/customTheme.dart'; import 'ChangeNotifierWidget.dart'; import 'SmoothScrollBuilder.dart'; @@ -37,7 +37,7 @@ void showWwiseProjectGeneratorPopup(String bnkPath) { class _WwiseProjectGeneratorPopup extends StatefulWidget { final String bnkPath; - String get bnkName => basename(bnkPath); + String get bnkName => basenameWithoutExtension(bnkPath); const _WwiseProjectGeneratorPopup(this.bnkPath); @@ -56,7 +56,9 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu final optNamePrefix = BoolProp(true, fileId: null); final optEvents = BoolProp(true, fileId: null); final optActions = BoolProp(true, fileId: null); - final savePath = StringProp("", fileId: null); + late final StringProp projectName; + late final StringProp savePath; + late final List bnkPaths; WwiseProjectGenerator? generator; bool hasStarted = false; final status = WwiseProjectGeneratorStatus(); @@ -69,14 +71,29 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu if (!widget.bnkName.contains("BGM")) optSeekTable.value = false; var prefs = PreferencesData(); - savePath.value = prefs.lastWwiseProjectDir?.value ?? ""; + projectName = StringProp(widget.bnkName, fileId: null); + savePath = StringProp(prefs.lastWwiseProjectDir?.value ?? "", fileId: null); status.logs.addListener(onNewLog); status.isDone.addListener(() => setState(() {})); + bnkPaths = [widget.bnkPath]; } @override void dispose() { + optAudioHierarchy.dispose(); + optWems.dispose(); + optStreaming.dispose(); + optStreamingPrefetch.dispose(); + optSeekTable.dispose(); + optTranslate.dispose(); + optNameId.dispose(); + optNamePrefix.dispose(); + optEvents.dispose(); + optActions.dispose(); + projectName.dispose(); + savePath.dispose(); status.dispose(); + logScrollController.dispose(); super.dispose(); } @@ -86,7 +103,7 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Generate Wwise project for ${widget.bnkName}", style: Theme.of(context).textTheme.titleLarge), + Text("Generate Wwise project", style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 15), if (!hasStarted) ..._makeOptions(context) @@ -112,10 +129,64 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu ("Object ID", optNameId), ]; return [ + const Text("Source BNKs:"), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: SmoothSingleChildScrollView( + child: Column( + children: [ + for (var (i, bnkPath) in bnkPaths.indexed) + Row( + key: Key(bnkPath), + children: [ + const SizedBox(width: 20), + Text(basename(bnkPath)), + const SizedBox(width: 10), + IconButton( + icon: const Icon(Icons.close, size: 16), + splashRadius: 16, + constraints: BoxConstraints.tight(const Size(30, 30)), + padding: EdgeInsets.zero, + onPressed: () { + bnkPaths.removeAt(i); + setState(() {}); + }, + ), + ], + ), + Row( + children: [ + const SizedBox(width: 15), + IconButton( + icon: const Icon(Icons.add, size: 16), + splashRadius: 16, + padding: EdgeInsets.zero, + constraints: BoxConstraints.tight(const Size(30, 30)), + onPressed: () async { + var files = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["bnk"], + allowMultiple: true, + dialogTitle: "Select BNKs", + ); + if (files == null) + return; + bnkPaths.addAll(files.paths.whereType()); + setState(() {}); + }, + ), + ], + ), + ], + ) + ), + ), + const SizedBox(height: 15), + const Text("Projects save path:"), Row( children: [ Expanded( - child: PrimaryPropTextField(prop: savePath, options: const PropTFOptions(hintText: "Projects save path")) + child: PrimaryPropTextField(prop: savePath) ), const SizedBox(width: 10), SmallButton( @@ -134,7 +205,17 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu ], ), const SizedBox(height: 15), - Text("Include from BNK:", style: Theme.of(context).textTheme.bodyMedium), + Row( + children: [ + const Text("Project name:"), + const SizedBox(width: 10), + Expanded( + child: UnderlinePropTextField(prop: projectName) + ), + ], + ), + const SizedBox(height: 15), + const Text("Include from BNK:"), const SizedBox(height: 2), for (var (label, prop) in labeledOptions) Row( @@ -144,14 +225,14 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu Expanded( child: GestureDetector( onTap: () => prop.value = !prop.value, - child: Text(label, style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis), + child: Text(label, overflow: TextOverflow.ellipsis), ), ), ], ), Row( children: [ - Text("Name settings: ", style: Theme.of(context).textTheme.bodyMedium), + const Text("Name settings: "), for (var (label, prop) in labeledNameOptions) Row( children: [ @@ -159,7 +240,7 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu const SizedBox(width: 5), GestureDetector( onTap: () => prop.value = !prop.value, - child: Text(label, style: Theme.of(context).textTheme.bodyMedium) + child: Text(label) ), ], ), @@ -258,7 +339,13 @@ class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopu ); hasStarted = true; setState(() {}); - generator = await WwiseProjectGenerator.generateFromBnk(widget.bnkPath, savePath.value, options, status); + generator = await WwiseProjectGenerator.generateFromBnks( + projectName.value, + bnkPaths, + savePath.value, + options, + status, + ); if (generator == null) return; setState(() {});