ReaWwise

REAPER extension
Log | Files | Refs | Submodules

DawWatcher.cpp (9858B)


      1 /*----------------------------------------------------------------------------------------
      2 
      3 Copyright (c) 2023 AUDIOKINETIC Inc.
      4 
      5 This file is licensed to use under the license available at:
      6 https://github.com/audiokinetic/ReaWwise/blob/main/License.txt (the "License").
      7 You may not use this file except in compliance with the License.
      8 
      9 Unless required by applicable law or agreed to in writing, software distributed
     10 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
     11 CONDITIONS OF ANY KIND, either express or implied.  See the License for the
     12 specific language governing permissions and limitations under the License.
     13 
     14 ----------------------------------------------------------------------------------------*/
     15 
     16 #include "DawWatcher.h"
     17 
     18 #include "Helpers/ImportHelper.h"
     19 #include "Model/IDs.h"
     20 #include "Model/Import.h"
     21 #include "Model/Waapi.h"
     22 
     23 #include <juce_gui_basics/juce_gui_basics.h>
     24 #include <optional>
     25 #include <unordered_map>
     26 
     27 namespace AK::WwiseTransfer
     28 {
     29 	struct PreviewOptions
     30 	{
     31 		Import::ContainerNameExistsOption containerNameExists;
     32 		juce::String projectPath;
     33 		juce::String originalsFolder;
     34 		bool waqlEnabled;
     35 		juce::String languageSubfolder;
     36 	};
     37 
     38 	DawWatcher::DawWatcher(juce::ValueTree appState, WaapiClient& waapiClient, DawContext& dawContext, int refreshInterval)
     39 		: applicationState(appState)
     40 		, hierarchyMapping(appState.getChildWithName(IDs::hierarchyMapping))
     41 		, previewItems(appState.getChildWithName(IDs::previewItems))
     42 		, importDestination(appState, IDs::importDestination, nullptr)
     43 		, originalsSubfolder(appState, IDs::originalsSubfolder, nullptr)
     44 		, containerNameExists(appState, IDs::containerNameExists, nullptr)
     45 		, previewLoading(appState, IDs::previewLoading, nullptr)
     46 		, sessionName(appState, IDs::sessionName, nullptr)
     47 		, projectPath(appState, IDs::projectPath, nullptr)
     48 		, originalsFolder(appState, IDs::originalsFolder, nullptr)
     49 		, languageSubfolder(appState, IDs::languageSubfolder, nullptr)
     50 		, waapiConnected(appState, IDs::waapiConnected, nullptr)
     51 		, dawContext(dawContext)
     52 		, waapiClient(waapiClient)
     53 		, lastImportItemsHash(0)
     54 		, refreshInterval(refreshInterval)
     55 		, previewOptionsChanged(false)
     56 	{
     57 		auto featureSupport = appState.getChildWithName(IDs::featureSupport);
     58 		waqlEnabled.referTo(featureSupport, IDs::waqlEnabled, nullptr);
     59 
     60 		applicationState.addListener(this);
     61 	}
     62 
     63 	DawWatcher::~DawWatcher()
     64 	{
     65 		applicationState.removeListener(this);
     66 	}
     67 
     68 	void DawWatcher::start()
     69 	{
     70 		startTimer(refreshInterval);
     71 	}
     72 
     73 	void DawWatcher::stop()
     74 	{
     75 		stopTimer();
     76 	}
     77 
     78 	void DawWatcher::timerCallback()
     79 	{
     80 		sessionName = dawContext.getSessionName();
     81 
     82 		if(dawContext.sessionChanged())
     83 		{
     84 			triggerAsyncUpdate();
     85 		}
     86 	}
     87 
     88 	void DawWatcher::handleAsyncUpdate()
     89 	{
     90 		const auto hierarchyMappingPath = ImportHelper::hierarchyMappingToPath(ImportHelper::valueTreeToHierarchyMappingNodeList(hierarchyMapping));
     91 		const auto importItems = dawContext.getItemsForPreview({importDestination, originalsSubfolder, hierarchyMappingPath});
     92 
     93 		const auto importItemsHash = ImportHelper::importPreviewItemsToHash(importItems);
     94 
     95 		if(importItemsHash != lastImportItemsHash || previewOptionsChanged)
     96 		{
     97 			auto pathParts = WwiseHelper::pathToPathParts(WwiseHelper::pathToPathWithoutObjectTypes(importDestination) +
     98 														  WwiseHelper::pathToPathWithoutObjectTypes(hierarchyMappingPath));
     99 
    100 			previewOptionsChanged = false;
    101 
    102 			std::set<juce::String> objectPaths;
    103 			std::unordered_map<juce::String, juce::ValueTree> pathToValueTreeMapping;
    104 
    105 			juce::ValueTree rootNode(IDs::previewItems);
    106 
    107 			// Build tree based on import items and their ancestors
    108 			for(const auto& importItem : importItems)
    109 			{
    110 				auto currentNode = rootNode;
    111 				int depth = 0;
    112 
    113 				for(const auto& ancestorPath : WwiseHelper::pathToAncestorPaths(importItem.path))
    114 				{
    115 					auto pathWithoutType = WwiseHelper::pathToPathWithoutObjectTypes(ancestorPath);
    116 					auto child = currentNode.getChildWithName(pathWithoutType);
    117 
    118 					if(!child.isValid())
    119 					{
    120 						objectPaths.insert(pathWithoutType);
    121 
    122 						auto name = WwiseHelper::pathToObjectName(pathWithoutType);
    123 						auto type = WwiseHelper::pathToObjectType(ancestorPath);
    124 
    125 						auto unresolvedWildcard = name.isEmpty() && pathParts[depth].isNotEmpty();
    126 
    127 						Import::PreviewItemNode previewItemNode{name, type, Import::ObjectStatus::New, "", Import::WavStatus::Unknown, unresolvedWildcard};
    128 						child = ImportHelper::previewItemNodeToValueTree(pathWithoutType, previewItemNode);
    129 
    130 						currentNode.appendChild(child, nullptr);
    131 						pathToValueTreeMapping[pathWithoutType] = child;
    132 					}
    133 
    134 					currentNode = child;
    135 					depth++;
    136 				}
    137 
    138 				auto pathWithoutType = WwiseHelper::pathToPathWithoutObjectTypes(importItem.path);
    139 				objectPaths.insert(pathWithoutType);
    140 
    141 				auto name = WwiseHelper::pathToObjectName(importItem.path);
    142 				auto type = WwiseHelper::pathToObjectType(importItem.path);
    143 
    144 				auto originalsWav = languageSubfolder + juce::File::getSeparatorChar() +
    145 				                    (importItem.originalsSubFolder.isNotEmpty() ? importItem.originalsSubFolder + juce::File::getSeparatorChar() : "") +
    146 				                    juce::File(importItem.audioFilePath).getFileName();
    147 
    148 				auto wavStatus = Import::WavStatus::Unknown;
    149 
    150 				if(originalsFolder.get().isNotEmpty())
    151 				{
    152 					auto absoluteWavPath = juce::File(originalsFolder).getChildFile(originalsWav);
    153 
    154 					if(absoluteWavPath.exists())
    155 						wavStatus = Import::WavStatus::Replaced;
    156 					else
    157 						wavStatus = Import::WavStatus::New;
    158 				}
    159 
    160 				auto unresolvedWildcard = name.isEmpty() && pathParts[depth].isNotEmpty();
    161 
    162 				Import::PreviewItemNode previewItemNode{name, type, Import::ObjectStatus::New, originalsWav, wavStatus, unresolvedWildcard};
    163 				auto child = ImportHelper::previewItemNodeToValueTree(pathWithoutType, previewItemNode);
    164 
    165 				currentNode.appendChild(child, nullptr);
    166 				pathToValueTreeMapping[pathWithoutType] = child;
    167 			}
    168 
    169 			auto onGetObjectAncestorsAndDescendants = [this, pathToValueTreeMapping, rootNode](const Waapi::Response<Waapi::ObjectResponseSet>& response)
    170 			{
    171 				if(response.status)
    172 				{
    173 					// Update original tree with information from existing objects
    174 					for(const auto& existingObject : response.result)
    175 					{
    176 						auto it = pathToValueTreeMapping.find(existingObject.path);
    177 
    178 						if(it != pathToValueTreeMapping.end())
    179 						{
    180 							auto previewItem = ImportHelper::valueTreeToPreviewItemNode(it->second);
    181 
    182 							if(existingObject.type != Wwise::ObjectType::Sound || containerNameExists == Import::ContainerNameExistsOption::UseExisting)
    183 								previewItem.objectStatus = Import::ObjectStatus::NoChange;
    184 							else if(containerNameExists == Import::ContainerNameExistsOption::Replace)
    185 								previewItem.objectStatus = Import::ObjectStatus::Replaced;
    186 							else if(containerNameExists == Import::ContainerNameExistsOption::CreateNew)
    187 								previewItem.objectStatus = Import::ObjectStatus::NewRenamed;
    188 
    189 							if(previewItem.type == Wwise::ObjectType::Unknown)
    190 							{
    191 								previewItem.type = existingObject.type;
    192 							}
    193 
    194 							juce::ValueTree localCopy = it->second;
    195 							localCopy.copyPropertiesFrom(ImportHelper::previewItemNodeToValueTree(existingObject.path, previewItem), nullptr);
    196 						}
    197 					}
    198 				}
    199 
    200 				if(!previewItems.isEquivalentTo(rootNode))
    201 					previewItems.copyPropertiesAndChildrenFrom(rootNode, nullptr);
    202 
    203 				previewLoading = false;
    204 			};
    205 
    206 			if(waapiConnected.get() && importDestination.get().isNotEmpty())
    207 			{
    208 				previewLoading = true;
    209 
    210 				if(waqlEnabled)
    211 					waapiClient.getObjectAncestorsAndDescendantsAsync(importDestination, onGetObjectAncestorsAndDescendants);
    212 				else
    213 					waapiClient.getObjectAncestorsAndDescendantsLegacyAsync(importDestination, onGetObjectAncestorsAndDescendants);
    214 			}
    215 			else
    216 			{
    217 				Waapi::Response<Waapi::ObjectResponseSet> emptyResponse;
    218 				onGetObjectAncestorsAndDescendants(emptyResponse);
    219 			}
    220 		}
    221 	}
    222 
    223 	void DawWatcher::valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property)
    224 	{
    225 		static std::initializer_list<juce::Identifier> properties{IDs::containerNameExists, IDs::projectPath, IDs::originalsFolder,
    226 			IDs::wwiseObjectsChanged, IDs::waqlEnabled, IDs::languageSubfolder, IDs::originalsFolder, IDs::importDestination, IDs::originalsSubfolder, IDs::waapiConnected};
    227 
    228 		if(treeWhosePropertyHasChanged == applicationState && std::find(properties.begin(), properties.end(), property) != properties.end() ||
    229 			treeWhosePropertyHasChanged.getType() == IDs::hierarchyMappingNode)
    230 		{
    231 			// Used to notify the next update iteration that the preview may contain items that have changed that would not be reflected
    232 			// in the importItems hash. Perhaps we can include these items in the hash in the future.
    233 			previewOptionsChanged = true;
    234 
    235 			if(property == IDs::wwiseObjectsChanged)
    236 				applicationState.setPropertyExcludingListener(this, IDs::wwiseObjectsChanged, false, nullptr);
    237 
    238 			triggerAsyncUpdate();
    239 		}
    240 	}
    241 
    242 	void DawWatcher::valueTreeChildAdded(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenAdded)
    243 	{
    244 		juce::ignoreUnused(childWhichHasBeenAdded);
    245 
    246 		if(parentTree.getType() == IDs::hierarchyMapping)
    247 		{
    248 			triggerAsyncUpdate();
    249 		}
    250 	}
    251 
    252 	void DawWatcher::valueTreeChildRemoved(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved)
    253 	{
    254 		juce::ignoreUnused(childWhichHasBeenRemoved, indexFromWhichChildWasRemoved);
    255 
    256 		if(parentTree.getType() == IDs::hierarchyMapping)
    257 		{
    258 			triggerAsyncUpdate();
    259 		}
    260 	}
    261 
    262 	void DawWatcher::valueTreeChildOrderChanged(juce::ValueTree& parentTreeWhoseChildrenHaveMoved, int oldIndex, int newIndex)
    263 	{
    264 		juce::ignoreUnused(oldIndex, newIndex);
    265 
    266 		if(parentTreeWhoseChildrenHaveMoved.getType() == IDs::hierarchyMapping)
    267 		{
    268 			triggerAsyncUpdate();
    269 		}
    270 	}
    271 } // namespace AK::WwiseTransfer