diff --git a/lib/stateManagement/hierarchy/types/BnkHierarchyEntry.dart b/lib/stateManagement/hierarchy/types/BnkHierarchyEntry.dart index ff72711..1100e42 100644 --- a/lib/stateManagement/hierarchy/types/BnkHierarchyEntry.dart +++ b/lib/stateManagement/hierarchy/types/BnkHierarchyEntry.dart @@ -11,7 +11,7 @@ import '../../../fileTypeUtils/audio/removePrefetchWems.dart'; import '../../../fileTypeUtils/audio/wemIdsToNames.dart'; import '../../../fileTypeUtils/audio/wwiseObjectPath.dart'; import '../../../utils/utils.dart'; -import '../../../utils/wwiseProjectGenerator/wwiseProjectGenerator.dart'; +import '../../../widgets/misc/wwiseProjectGeneratorPopup.dart'; import '../../Property.dart'; import '../../openFiles/types/WemFileData.dart'; import '../../undoable.dart'; @@ -511,7 +511,7 @@ class BnkHierarchyEntry extends GenericFileHierarchyEntry { return [ HierarchyEntryAction( name: "Create Wwise project", - action: () => WwiseProjectGenerator.generateFromBnk(path, r"D:\delete\wwise project gen"), + action: () => showWwiseProjectGeneratorPopup(path), ), ...super.getActions() ]; diff --git a/lib/stateManagement/preferencesData.dart b/lib/stateManagement/preferencesData.dart index f1f1bbf..fb08cdb 100644 --- a/lib/stateManagement/preferencesData.dart +++ b/lib/stateManagement/preferencesData.dart @@ -86,6 +86,7 @@ class PreferencesData extends OpenFileData { SavableProp? wwise2012CliPath; SavableProp? lastCpkExtractDir; SavableProp? lastSearchDir; + SavableProp? lastWwiseProjectDir; PreferencesData._() : prefsFuture = SharedPreferences.getInstance(), @@ -134,6 +135,7 @@ class PreferencesData extends OpenFileData { wwise2012CliPath = SavableProp("wwise2012CliPath", _prefs!, ""); lastCpkExtractDir = SavableProp("lastCpkExtractDir", _prefs!, ""); lastSearchDir = SavableProp("lastSearchDir", _prefs!, ""); + lastWwiseProjectDir = SavableProp("lastWwiseProjectDir", _prefs!, ""); await super.load(); _loadingState = LoadingState.loaded; diff --git a/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart b/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart index fb5fc2c..2986aa7 100644 --- a/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart +++ b/lib/utils/wwiseProjectGenerator/elements/hierarchyBaseElements.dart @@ -155,7 +155,7 @@ class WwiseHierarchyElement extends pannerProps.add(WwiseProperty(prop.name, prop.type, value: value.number.toString())); } else { - project.log(WwiseLogSeverity.error, "Unknown property id $propId on ${chunk.uid}"); + project.log(WwiseLogSeverity.warning, "Unknown property id $propId on ${chunk.uid}"); } continue; } @@ -164,7 +164,7 @@ class WwiseHierarchyElement extends for (var (propId, value) in rangedProps) { var prop = _propIdToConfig[propId]; if (prop == null) { - project.log(WwiseLogSeverity.error, "Unknown ranged property id $propId on ${chunk.uid}"); + project.log(WwiseLogSeverity.warning, "Unknown ranged property id $propId on ${chunk.uid}"); continue; } properties.add(WwiseProperty( @@ -177,7 +177,7 @@ class WwiseHierarchyElement extends for (var rtpc in baseParams.rtpc.rtpc) { var prop = _rtpcPropIdToName[rtpc.paramID]; if (prop == null) { - project.log(WwiseLogSeverity.error, "Unknown rtpc id ${rtpc.paramID} on ${chunk.uid}"); + project.log(WwiseLogSeverity.warning, "Unknown rtpc id ${rtpc.paramID} on ${chunk.uid}"); continue; } properties.add(WwiseProperty(prop.name, prop.type, project: project, rtpcs: [rtpc])); @@ -194,7 +194,7 @@ class WwiseHierarchyElement extends continue; var effectRef = project.lookupElement(idFnv: effect.fxID) as WwiseEffect?; if (effectRef == null) { - project.log(WwiseLogSeverity.error, "Could not locate effect id ${effect.fxID}"); + project.log(WwiseLogSeverity.warning, "Could not locate effect id ${effect.fxID}"); continue; } if (effect.bIsShareSet != 0) @@ -274,7 +274,7 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { element = WwiseMusicSwitch(project: project, wuId: imhWu.id, chunk: chunk); } else { - project.log(WwiseLogSeverity.error, "Unknown chunk type ${chunk.runtimeType}"); + project.log(WwiseLogSeverity.warning, "Unknown chunk type ${chunk.runtimeType}"); } if (element != null) { @@ -294,7 +294,7 @@ Future saveHierarchyBaseElements(WwiseProjectGenerator project) async { for (var (id, parentId, element) in elements.values) { var parent = elements[parentId]; if (parent == null && parentId != 0) { - project.log(WwiseLogSeverity.error, "Could not find parent $parentId for $id"); + project.log(WwiseLogSeverity.warning, "Could not find parent $parentId for $id"); continue; } if (parent != null) diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseAction.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseAction.dart index 9b9262e..295a45d 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseAction.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseAction.dart @@ -114,7 +114,7 @@ class WwiseAction extends WwiseElement { ? project.buses[action.initialParams.idExt] : project.lookupElement(idFnv: action.initialParams.idExt); if (action.initialParams.idExt != 0 && lookup == null) - project.log(WwiseLogSeverity.error, "Unknown action target ${action.initialParams.idExt}"); + project.log(WwiseLogSeverity.warning, "Unknown action target ${action.initialParams.idExt}"); elements.add(lookup); } // exceptions @@ -129,7 +129,7 @@ class WwiseAction extends WwiseElement { ? project.buses[id] : project.lookupElement(idFnv: id); if (lookup == null && id != 0) - project.log(WwiseLogSeverity.error, "Unknown action exception $id"); + project.log(WwiseLogSeverity.warning, "Unknown action exception $id"); elements.add(lookup); } } @@ -172,7 +172,7 @@ class WwiseAction extends WwiseElement { for (var (prop: prop, value: value) in valueProps) { var propInfo = _bnkPropToName[prop]; if (propInfo == null) { - project.log(WwiseLogSeverity.error, "Unknown value property 0x${prop.toRadixString(16)}"); + project.log(WwiseLogSeverity.warning, "Unknown value property 0x${prop.toRadixString(16)}"); continue; } properties.add(WwiseProperty(propInfo.name, propInfo.type, value: (value.number / propInfo.div).toString())); @@ -180,7 +180,7 @@ class WwiseAction extends WwiseElement { for (var (prop: prop, value: (min, max)) in rangedProps) { var propInfo = _bnkPropToName[prop]; if (propInfo == null) { - project.log(WwiseLogSeverity.error, "Unknown ranged property 0x${prop.toRadixString(16)}"); + project.log(WwiseLogSeverity.warning, "Unknown ranged property 0x${prop.toRadixString(16)}"); continue; } properties.add(WwiseProperty( diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart index 1aa1e5e..b215641 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseAttenuations.dart @@ -55,7 +55,7 @@ class WwiseAttenuation extends WwiseElement { for (var rtpc in attenuation.rtpcs) { var config = _rtpcPropConfig[rtpc.paramID]; if (config == null) { - project.log(WwiseLogSeverity.error, "Unknown rtpc curve id ${rtpc.paramID}"); + project.log(WwiseLogSeverity.warning, "Unknown rtpc curve id ${rtpc.paramID}"); continue; } properties.add(WwiseProperty(config.name, config.type, project: project, rtpcs: [rtpc])); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart index 6476a29..0311562 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseEffect.dart @@ -38,7 +38,7 @@ class WwiseEffect extends WwiseElement { for (var rtpc in effect.rtpc.rtpc) { var propConfig = config.rtpcProps[rtpc.paramID]; if (propConfig == null) { - project.log(WwiseLogSeverity.error, "Unknown rtpc curve id ${rtpc.paramID} for effect plugin ${config.name}"); + project.log(WwiseLogSeverity.warning, "Unknown rtpc curve id ${rtpc.paramID} for effect plugin ${config.name}"); continue; } properties.add(WwiseProperty(propConfig.name, propConfig.type, project: project, rtpcs: [rtpc])); @@ -48,12 +48,12 @@ class WwiseEffect extends WwiseElement { static WwiseEffect? fromId({required String wuId, required WwiseProjectGenerator project, required int id}) { var effect = project.hircChunkById(id); if (effect == null) { - project.log(WwiseLogSeverity.error, "Could not locate effect id $id"); + project.log(WwiseLogSeverity.warning, "Could not locate effect id $id"); return null; } var config = _pluginConfigs[effect.fxId]; if (config == null) { - project.log(WwiseLogSeverity.error, "Unknown effect id ${effect.fxId}"); + project.log(WwiseLogSeverity.warning, "Unknown effect id ${effect.fxId}"); return null; } return WwiseEffect(wuId: wuId, project: project, effect: effect, config: config); @@ -228,7 +228,7 @@ Future saveEffectsIntoWu(WwiseProjectGenerator project) async { continue; var config = _pluginConfigs[effect.fxId]; if (config == null) { - project.log(WwiseLogSeverity.error, "Unknown effect id ${effect.fxId}"); + project.log(WwiseLogSeverity.warning, "Unknown effect id ${effect.fxId}"); continue; } var effectElement = WwiseEffect(wuId: project.effectsWu.id, project: project, effect: effect, config: config); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart index 0c91238..b25ef89 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseEvents.dart @@ -20,7 +20,7 @@ Future saveEventsHierarchy(WwiseProjectGenerator project) async { shortIdHint: event.uid, children: [ for (var actionId in event.ids) - if (actionsMap.containsKey(actionId)) + if (project.options.actions && actionsMap.containsKey(actionId)) WwiseAction(wuId: project.eventsWu.id, project: project, action: actionsMap[actionId]!) ] ), event.uid); diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseMusicTrack.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseMusicTrack.dart index 5f94231..9207f3f 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseMusicTrack.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseMusicTrack.dart @@ -37,9 +37,9 @@ class WwiseMusicTrack extends WwiseHierarchyElement { name: "${wemIdsToNames[chunk.sources.first.sourceID] ?? wemIdsToNames[chunk.sources.first.fileID] ?? chunk.uid.toString()} Music Track", shortId: chunk.uid, properties: [ - if ((chunk.sources.firstOrNull?.streamType ?? 0) >= 1) + if (project.options.streaming && (chunk.sources.firstOrNull?.streamType ?? 0) >= 1) WwiseProperty("IsStreamingEnabled", "bool", values: ["True"]), - if ((chunk.sources.firstOrNull?.streamType ?? 0) == 2) + if (project.options.streaming && (chunk.sources.firstOrNull?.streamType ?? 0) == 2) WwiseProperty("IsZeroLantency", "bool", values: ["True"]), if (chunk.iLookAheadTime != 100) WwiseProperty("LookAheadTime", "int16", value: chunk.iLookAheadTime.toString()), diff --git a/lib/utils/wwiseProjectGenerator/elements/wwisePositioningInfo.dart b/lib/utils/wwiseProjectGenerator/elements/wwisePositioningInfo.dart index c04899a..949c43a 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwisePositioningInfo.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwisePositioningInfo.dart @@ -18,7 +18,7 @@ XmlElement? makeWwisePositioningInfo(WwiseProjectGenerator project, WwiseElement if ((positioning.attenuationID ?? 0) != 0) { var attenuation = project.lookupElement(idFnv: positioning.attenuationID!) as WwiseAttenuation?; if (attenuation == null) { - project.log(WwiseLogSeverity.error, "Attenuation with ID ${positioning.attenuationID} not found"); + project.log(WwiseLogSeverity.warning, "Attenuation with ID ${positioning.attenuationID} not found"); } else { children.add(makeXmlElement(name: "AttenuationInfo", children: [ makeXmlElement( diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart index 85698aa..746941c 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseSound.dart @@ -18,9 +18,9 @@ class WwiseSound extends WwiseHierarchyElement { shortId: chunk.uid, additionalAttributes: { "Type": chunk.bankData.mediaInformation.uSourceBits & 1 == 0 ? "SoundFX" : "Voice"}, properties: [ - if (chunk.bankData.streamType >= 1) + if (project.options.streaming && chunk.bankData.streamType >= 1) WwiseProperty("IsStreamingEnabled", "bool", values: ["True"]), - if (chunk.bankData.streamType == 2) + if (project.options.streaming && chunk.bankData.streamType == 2) WwiseProperty("IsZeroLantency", "bool", values: ["True"]), ], children: [ diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart index f166a3d..0e6f50c 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseStateInfo.dart @@ -18,7 +18,7 @@ XmlElement? makeWwiseStatInfo(WwiseProjectGenerator project, List() @@ -52,7 +52,7 @@ XmlElement? makeWwiseStatInfo(WwiseProjectGenerator project, List 0 && src == null) { - project.log(WwiseLogSeverity.error, "Transition source not found: ${wwiseIdToStr(transition.srcID)}"); + project.log(WwiseLogSeverity.warning, "Transition source not found: ${wwiseIdToStr(transition.srcID)}"); return null; } if (transition.dstID > 0 && dest == null) { - project.log(WwiseLogSeverity.error, "Transition destination not found: ${wwiseIdToStr(transition.dstID)}"); + project.log(WwiseLogSeverity.warning, "Transition destination not found: ${wwiseIdToStr(transition.dstID)}"); return null; } if (transition.dstRule.uJumpToID > 0 && destPlItem == null) { - project.log(WwiseLogSeverity.error, "Transition destination playlist item not found: ${wwiseIdToStr(transition.dstRule.uJumpToID)}"); + 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; diff --git a/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart b/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart index 80bd526..e3405ef 100644 --- a/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart +++ b/lib/utils/wwiseProjectGenerator/elements/wwiseTriggers.dart @@ -54,7 +54,7 @@ XmlElement? makeStingerList(WwiseProjectGenerator project, String wuId, List> prepareWwiseAudioFiles(WwiseProjectGenerator pr 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; @@ -83,7 +86,7 @@ Future> prepareWwiseAudioFiles(WwiseProjectGenerator pr if (wemPath == null) { var file = internalWemFiles[srcId]; if (file == null) { - project.log(WwiseLogSeverity.error, "WEM file ${wwiseIdToStr(srcId, alwaysIncludeId: true)} is not indexed or found in BNK file"); + 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"); diff --git a/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart b/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart index 888a9d8..6a34e8e 100644 --- a/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart +++ b/lib/utils/wwiseProjectGenerator/wwiseProjectGenerator.dart @@ -1,8 +1,9 @@ - +import 'dart:async'; import 'dart:io'; import 'package:archive/archive_io.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart'; import 'package:xml/xml.dart'; @@ -12,9 +13,10 @@ 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'; -import '../utils.dart'; +import '../../stateManagement/listNotifier.dart'; import 'elements/hierarchyBaseElements.dart'; import 'elements/wwiseAttenuations.dart'; import 'elements/wwiseBus.dart'; @@ -31,18 +33,73 @@ import 'wwiseAudioFilesPrepare.dart'; import 'wwiseElement.dart'; import 'wwiseElementBase.dart'; import 'wwiseIdGenerator.dart'; +import 'wwiseProperty.dart'; + + +class WwiseProjectGeneratorOptions { + final bool audioHierarchy; + final bool wems; + final bool streaming; + final bool seekTable; + final bool translate; + final bool events; + final bool actions; + + WwiseProjectGeneratorOptions({ + this.audioHierarchy = true, + this.wems = true, + this.streaming = true, + this.seekTable = true, + this.translate = true, + this.events = true, + this.actions = true, + }); + + WwiseProjectGeneratorOptions copyWith({ + bool? audioHierarchy, + bool? gameSyncs, + bool? wems, + bool? streaming, + bool? seekTable, + bool? translate, + bool? events, + bool? actions, + }) { + return WwiseProjectGeneratorOptions( + audioHierarchy: audioHierarchy ?? this.audioHierarchy, + wems: wems ?? this.wems, + streaming: streaming ?? this.streaming, + seekTable: seekTable ?? this.seekTable, + translate: translate ?? this.translate, + events: events ?? this.events, + actions: actions ?? this.actions, + ); + } +} +class WwiseProjectGeneratorStatus { + final logs = ValueListNotifier([], fileId: null); + final currentMsg = ValueNotifier(""); + final isDone = ValueNotifier(false); + + void dispose() { + logs.dispose(); + currentMsg.dispose(); + isDone.dispose(); + } +} class WwiseProjectGenerator { final String projectName; final String projectPath; final BnkFile bnk; + final WwiseProjectGeneratorOptions options; + final WwiseProjectGeneratorStatus status; late final Map> bnkFolders; final List _hircChunks; final Map _elements = {}; final Map shortToFullId = {}; final WwiseIdGenerator idGen; - final List _logs = []; final Map soundFiles = {}; final Map stateGroups = {}; final Map switchGroups = {}; @@ -64,7 +121,7 @@ class WwiseProjectGenerator { late final WwiseWorkUnit triggersWu; late final String language; - WwiseProjectGenerator(this.projectName, this.projectPath, this.bnk, this._hircChunks) + 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"; @@ -81,18 +138,25 @@ class WwiseProjectGenerator { bnkStateChunks[state.uid] = state; } - static Future generateFromBnk(String bnkPath, String savePath) async { + static Future generateFromBnk(String bnkPath, 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()) { - await Directory(projectPath).delete(recursive: true); + try { + await Directory(projectPath).delete(recursive: true); + } catch (e) { + messageLog.add("$e"); + status.logs.add(WwiseLog(WwiseLogSeverity.error, "Failed to delete existing project directory")); + return null; + } } if (await File(projectPath).exists()) { - showToast("Error: $projectPath is a file"); - return; + status.logs.add(WwiseLog(WwiseLogSeverity.error, "$projectPath is a file")); + return null; } await Directory(projectPath).create(recursive: true); // extract @@ -110,23 +174,28 @@ class WwiseProjectGenerator { var bnk = BnkFile.read(await ByteDataWrapper.fromFile(bnkPath)); var hirc = bnk.chunks.whereType().firstOrNull; if (hirc == null) { - showToast("Error: BNK file has no HIRC chunk"); - return; + 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); - await generator.run(); - showToast("Generated wwise project $projectName"); + var generator = WwiseProjectGenerator(projectName, projectPath, bnk, hircChunks, options, status); + unawaited(generator.run() + .catchError((e, st) { + messageLog.add("$e\n$st"); + status.currentMsg.value = "Failed to generate Wwise project"; + }) + .whenComplete(() => isLoadingStatus.popIsLoading())); + return generator; } catch (e, st) { messageLog.add("$e\n$st"); - showToast("Failed to generate Wwise project"); - } finally { - isLoadingStatus.popIsLoading(); + status.logs.add(WwiseLog(WwiseLogSeverity.error, "Failed to generate Wwise project")); } + return null; } Future run() async { + status.currentMsg.value = "Generating project..."; var unknownChunks = hircChunksByType().toList(); if (unknownChunks.isNotEmpty) { var chunkTypes = unknownChunks.map((c) => c.type).toSet(); @@ -163,13 +232,24 @@ class WwiseProjectGenerator { saveEffectsIntoWu(this), saveAttenuationsIntoWu(this), saveBusesIntoWu(this), - prepareWwiseAudioFiles(this).then((files) => soundFiles.addAll(files)), + if (options.seekTable) + _enableSeekTable(), + if (options.wems) + prepareWwiseAudioFiles(this).then((files) => soundFiles.addAll(files)), makeWwiseSoundBank(this), ]); idGen.init(this); - await saveHierarchyBaseElements(this); - await saveEventsHierarchy(this); + status.currentMsg.value = "Generating hierarchies..."; + + if (options.audioHierarchy) + await saveHierarchyBaseElements(this); + if (options.events) + await saveEventsHierarchy(this); + + log(WwiseLogSeverity.info, "Project generated successfully"); + status.currentMsg.value = ""; + status.isDone.value = true; } Iterable get hircChunks => _hircChunks; @@ -194,7 +274,7 @@ class WwiseProjectGenerator { void log(WwiseLogSeverity severity, String message) { messageLog.add(message); - _logs.add(WwiseLog(severity, message)); + status.logs.add(WwiseLog(severity, message)); } String? getComment(int? id) { @@ -203,9 +283,28 @@ class WwiseProjectGenerator { var comment = wwiseIdToNote[id]; if (comment == null) return null; - if (true) // TODO config + if (options.translate) return japToEng[comment] ?? comment; - // return comment; + return comment; + } + + Future _enableSeekTable() async { + var wuPath = join(projectPath, "Conversion Settings", "Factory Conversion Settings.wwu"); + var wuDoc = XmlDocument.parse(await File(wuPath).readAsString()); + var conversion = wuDoc.rootElement + .findAllElements("Conversion") + .where((e) => e.getAttribute("Name") == "Vorbis Quality High") + .first; + var pluginInfoList = conversion.findElements("ConversionPluginInfoList").first; + var conversionPluginInfo = pluginInfoList + .findElements("ConversionPluginInfo") + .where((e) => e.getAttribute("Platform") == "Windows") + .first; + var conversionPlugin = conversionPluginInfo.findElements("ConversionPlugin").first; + conversionPlugin.children.add(WwisePropertyList([ + WwiseProperty("SeekTableGranularity", "int32", value: "0") + ]).toXml()); + await File(wuPath).writeAsString(wuDoc.toPrettyString()); } } diff --git a/lib/widgets/misc/preferencesEditor.dart b/lib/widgets/misc/preferencesEditor.dart index dd978a1..a860e15 100644 --- a/lib/widgets/misc/preferencesEditor.dart +++ b/lib/widgets/misc/preferencesEditor.dart @@ -300,6 +300,12 @@ class _PreferencesEditorState extends ChangeNotifierState { ), ], ), + const Row( + children: [ + SizedBox(width: 20,), + Text("Folder where sound stream cpk files are extracted to"), + ], + ), RowSeparated( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lib/widgets/misc/wwiseProjectGeneratorPopup.dart b/lib/widgets/misc/wwiseProjectGeneratorPopup.dart new file mode 100644 index 0000000..0e8e6fe --- /dev/null +++ b/lib/widgets/misc/wwiseProjectGeneratorPopup.dart @@ -0,0 +1,255 @@ + +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:path/path.dart'; + +import '../../main.dart'; +import '../../stateManagement/Property.dart'; +import '../../stateManagement/preferencesData.dart'; +import '../../utils/utils.dart'; +import '../../utils/wwiseProjectGenerator/wwiseProjectGenerator.dart'; +import '../propEditors/boolPropCheckbox.dart'; +import '../propEditors/primaryPropTextField.dart'; +import '../propEditors/propTextField.dart'; +import '../theme/customTheme.dart'; +import 'ChangeNotifierWidget.dart'; +import 'SmoothScrollBuilder.dart'; +import 'smallButton.dart'; + +void showWwiseProjectGeneratorPopup(String bnkPath) { + showDialog( + context: getGlobalContext(), + builder: (context) => Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + backgroundColor: getTheme(context).sidebarBackgroundColor, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: _WwiseProjectGeneratorPopup(bnkPath) + ), + ), + ) + ); +} + +class _WwiseProjectGeneratorPopup extends StatefulWidget { + final String bnkPath; + String get bnkName => basename(bnkPath); + + const _WwiseProjectGeneratorPopup(this.bnkPath); + + @override + State<_WwiseProjectGeneratorPopup> createState() => __WwiseProjectGeneratorPopupState(); +} + +class __WwiseProjectGeneratorPopupState extends State<_WwiseProjectGeneratorPopup> { + final optAudioHierarchy = BoolProp(true, fileId: null); + final optWems = BoolProp(true, fileId: null); + final optStreaming = BoolProp(true, fileId: null); + final optSeekTable = BoolProp(true, fileId: null); + final optTranslate = BoolProp(true, fileId: null); + final optEvents = BoolProp(true, fileId: null); + final optActions = BoolProp(true, fileId: null); + final List<(String, BoolProp)> labeledOptions = []; + final savePath = StringProp("", fileId: null); + WwiseProjectGenerator? generator; + bool hasStarted = false; + final status = WwiseProjectGeneratorStatus(); + final logScrollController = ScrollController(); + bool isScrollQueue = false; + + @override + void initState() { + super.initState(); + if (!widget.bnkName.contains("BGM")) + optSeekTable.value = false; + labeledOptions.addAll([ + ("Actor-Mixer & Interactive Music Hierarchy", optAudioHierarchy), + ("Events", optEvents), + ("Actions", optActions), + ("WAV sources (requires \"WEM Extract Directory\" from settings)", optWems), + ("Use seek table for wems", optSeekTable), + ("Copy streaming settings", optStreaming), + ("Translate Japanese notes", optTranslate), + ]); + var prefs = PreferencesData(); + savePath.value = prefs.lastWwiseProjectDir?.value ?? ""; + status.logs.addListener(onNewLog); + status.isDone.addListener(() => setState(() {})); + } + + @override + void dispose() { + status.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Generate Wwise project for ${widget.bnkName}", style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 15), + if (!hasStarted) + ..._makeOptions(context) + else + ..._makeStatus(context), + ], + ); + } + + List _makeOptions(BuildContext context) { + return [ + Row( + children: [ + Expanded( + child: PrimaryPropTextField(prop: savePath, options: const PropTFOptions(hintText: "Projects save path")) + ), + const SizedBox(width: 10), + SmallButton( + constraints: BoxConstraints.tight(const Size(30, 30)), + onPressed: () async { + var dir = await FilePicker.platform.getDirectoryPath( + dialogTitle: "Select project save path", + ); + if (dir == null) + return; + savePath.value = dir; + PreferencesData().lastWwiseProjectDir!.value = dir; + }, + child: const Icon(Icons.folder, size: 17,), + ) + ], + ), + const SizedBox(height: 15), + Text("Include from BNK:", style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 2), + for (var (label, prop) in labeledOptions) + Row( + children: [ + BoolPropCheckbox(prop: prop), + const SizedBox(width: 10), + Expanded( + child: GestureDetector( + onTap: () => prop.value = !prop.value, + child: Text(label, style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis), + ), + ), + ], + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: generate, + style: getTheme(context).dialogPrimaryButtonStyle, + child: const Text("Generate"), + ), + ), + ]; + } + + List _makeStatus(BuildContext context) { + return [ + SizedBox( + height: 300, + child: SmoothSingleChildScrollView( + controller: logScrollController, + child: ListenableBuilder( + listenable: status.logs, + builder: (context, _) { + return SelectionArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (var log in status.logs) + Text.rich( + TextSpan( + children: [ + TextSpan( + text: log.severity.toString().split(".").last.toUpperCase(), + style: TextStyle(color: getSeverityColor(context, log.severity), fontWeight: FontWeight.bold), + ), + TextSpan(text: " ${log.message}"), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ChangeNotifierBuilder( + notifier: status.currentMsg, + builder: (context) => Text(status.currentMsg.value, style: Theme.of(context).textTheme.bodyLarge) + ), + if (status.isDone.value) + ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: getTheme(context).dialogSecondaryButtonStyle, + child: const Text("Close"), + ), + ], + ) + ]; + } + + void generate() async { + if (!await Directory(savePath.value).exists()) { + showToast("Project save path is invalid"); + return; + } + var options = WwiseProjectGeneratorOptions( + audioHierarchy: optAudioHierarchy.value, + wems: optWems.value, + streaming: optStreaming.value, + seekTable: optSeekTable.value, + translate: optTranslate.value, + events: optEvents.value, + actions: optActions.value, + ); + hasStarted = true; + setState(() {}); + generator = await WwiseProjectGenerator.generateFromBnk(widget.bnkPath, savePath.value, options, status); + if (generator == null) + return; + setState(() {}); + } + + void onNewLog() { + if (isScrollQueue) + return; + isScrollQueue = true; + waitForNextFrame().then((_) { + try { + logScrollController.jumpTo(logScrollController.position.maxScrollExtent); + // ignore: empty_catches + } catch (e) {} + isScrollQueue = false; + }); + } + + Color getSeverityColor(BuildContext context, WwiseLogSeverity severity) { + switch (severity) { + case WwiseLogSeverity.info: + return getTheme(context).textColor!; + case WwiseLogSeverity.warning: + return Colors.orange; + case WwiseLogSeverity.error: + return Colors.red; + } + } +}