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