WaapiClient.cpp (27130B)
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 "WaapiClient.h" 17 18 #include "Helpers/ImportHelper.h" 19 #include "Model/IDs.h" 20 21 #include <JSONHelpers.h> 22 #include <juce_events/juce_events.h> 23 #include <set> 24 25 namespace AK::WwiseTransfer 26 { 27 namespace WaapiCommands 28 { 29 static constexpr const char* const projectLoaded = "ak.wwise.core.project.loaded"; 30 static constexpr const char* const projectPostClosed = "ak.wwise.core.project.postClosed"; 31 static constexpr const char* const objectPostDeleted = "ak.wwise.core.object.postDeleted"; 32 static constexpr const char* const objectNameChanged = "ak.wwise.core.object.nameChanged"; 33 static constexpr const char* const objectCreated = "ak.wwise.core.object.created"; 34 static constexpr const char* const objectGet = "ak.wwise.core.object.get"; 35 static constexpr const char* const audioImport = "ak.wwise.core.audio.import"; 36 static constexpr const char* const getProjectInfo = "ak.wwise.core.getProjectInfo"; 37 static constexpr const char* const undoBeginGroup = "ak.wwise.core.undo.beginGroup"; 38 static constexpr const char* const undoCancelGroup = "ak.wwise.core.undo.cancelGroup"; 39 static constexpr const char* const undoEndGroup = "ak.wwise.core.undo.endGroup"; 40 static constexpr const char* const objectPasteProperties = "ak.wwise.core.object.pasteProperties"; 41 static constexpr const char* const getInfo = "ak.wwise.core.getInfo"; 42 static constexpr const char* const getSelectedObjects = "ak.wwise.ui.getSelectedObjects"; 43 static constexpr const char* const commandsExecute = "ak.wwise.ui.commands.execute"; 44 } // namespace WaapiCommands 45 46 namespace WaapiURIs 47 { 48 static constexpr const char* const unknownObject = "ak.wwise.query.unknown_object"; 49 } 50 51 namespace WaapiMessages 52 { 53 static constexpr const char* const objectNotFound = "Object not found"; 54 } 55 56 WaapiClientWatcher::WaapiClientWatcher(juce::ValueTree appState, WaapiClient& waapiClient, WaapiClientWatcherConfig&& waapiClientWatcherConfig) 57 : juce::Thread("WaapiService") 58 , applicationState(appState) 59 , projectId(applicationState, IDs::projectId, nullptr) 60 , waapiConnected(applicationState, IDs::waapiConnected, nullptr) 61 , wwiseObjectsChanged(applicationState, IDs::wwiseObjectsChanged, nullptr) 62 , waapiClient(waapiClient) 63 , waapiClientWatcherConfig(waapiClientWatcherConfig) 64 , connectionRetryDelay(waapiClientWatcherConfig.MinConnectionRetryDelay) 65 , languages(applicationState.getChildWithName(IDs::languages)) 66 , ip(waapiClientWatcherConfig.Ip) 67 , port(waapiClientWatcherConfig.Port) 68 { 69 } 70 71 void WaapiClientWatcher::start() 72 { 73 startThread(); 74 } 75 76 void WaapiClientWatcher::stop() 77 { 78 stopThread(-1); 79 } 80 81 void WaapiClientWatcher::changeParameters(const juce::String& ip, int port) 82 { 83 { 84 std::lock_guard lock(guiMutex); 85 this->ip = ip; 86 this->port = port; 87 shouldReconnect = true; 88 } 89 notify(); 90 } 91 92 void WaapiClientWatcher::run() 93 { 94 using namespace WwiseAuthoringAPI; 95 auto onDisconnect = [this] 96 { 97 juce::Logger::writeToLog("Disconnected from waapi"); 98 99 setWaapiConnected(false); 100 }; 101 102 auto onProjectLoaded = [this](auto, auto) 103 { 104 juce::Logger::writeToLog("Received project loaded event"); 105 106 setProjectId(""); 107 }; 108 109 auto onProjectPostClosed = [this](auto, auto) 110 { 111 juce::Logger::writeToLog("Received project post close event"); 112 }; 113 114 auto onObjectEvent = [this](auto, auto) 115 { 116 juce::Logger::writeToLog("Received object related event"); 117 118 setWwiseObjectsChanged(true); 119 }; 120 121 while(!threadShouldExit()) 122 { 123 { 124 std::unique_lock lock(guiMutex); 125 if(shouldReconnect) 126 { 127 waapiClientWatcherConfig.Ip = ip; 128 waapiClientWatcherConfig.Port = port; 129 shouldReconnect = false; 130 lock.unlock(); 131 disconnectFromWaapi(); 132 } 133 } 134 135 int waitTime = waapiClientWatcherConfig.ConnectionMonitorDelay; 136 137 if(!waapiClient.isConnected()) 138 { 139 if(waapiClient.connect(static_cast<const char*>(waapiClientWatcherConfig.Ip.toUTF8()), waapiClientWatcherConfig.Port, onDisconnect)) 140 { 141 juce::Logger::writeToLog("Connected to waapi"); 142 143 connectionRetryDelay = waapiClientWatcherConfig.MinConnectionRetryDelay; 144 145 AkJson subscribeResult; 146 if(waapiClient.subscribe(WaapiCommands::projectLoaded, AkJson::Map{}, onProjectLoaded, projectLoadedSubscriptionId, subscribeResult)) 147 { 148 juce::Logger::writeToLog("Subscribed to project loaded"); 149 } 150 else 151 { 152 juce::Logger::writeToLog("Failed to subscribed to project loaded"); 153 } 154 155 if(waapiClient.subscribe(WaapiCommands::projectPostClosed, AkJson::Map{}, onProjectPostClosed, projectPostClosedSubscriptionId, subscribeResult)) 156 { 157 juce::Logger::writeToLog("Subscribed to project unloaded"); 158 } 159 else 160 { 161 juce::Logger::writeToLog("Failed to subscribed to project post closed"); 162 } 163 164 if(waapiClient.subscribe(WaapiCommands::objectCreated, AkJson::Map{}, onObjectEvent, objectCreatedEventSubscriptionId, subscribeResult)) 165 { 166 juce::Logger::writeToLog("Subscribed to object created"); 167 } 168 else 169 { 170 juce::Logger::writeToLog("Failed to subscribed to object created"); 171 } 172 173 if(waapiClient.subscribe(WaapiCommands::objectPostDeleted, AkJson::Map{}, onObjectEvent, objectPostDeletedEventSubscriptionId, subscribeResult)) 174 { 175 juce::Logger::writeToLog("Subscribed to object postDeleted"); 176 } 177 else 178 { 179 juce::Logger::writeToLog("Failed to subscribed to object postDeleted"); 180 } 181 182 if(waapiClient.subscribe(WaapiCommands::objectNameChanged, AkJson::Map{}, onObjectEvent, objectNameChangedEventSubscriptionId, subscribeResult)) 183 { 184 juce::Logger::writeToLog("Subscribed to object nameChanged"); 185 } 186 else 187 { 188 juce::Logger::writeToLog("Failed to subscribed to object nameChanged"); 189 } 190 191 setWaapiConnected(true); 192 } 193 else 194 { 195 waitTime = connectionRetryDelay; 196 connectionRetryDelay = juce::jmin(connectionRetryDelay * 2, waapiClientWatcherConfig.MaxConnectionRetryDelay); 197 198 juce::String errorMessage("Error trying to connect to waapi ... Retrying in "); 199 200 errorMessage << connectionRetryDelay << " ms"; 201 202 juce::Logger::writeToLog(errorMessage); 203 } 204 } 205 206 wait(waitTime); 207 } 208 if(waapiClient.isConnected()) 209 { 210 disconnectFromWaapi(); 211 } 212 } 213 void WaapiClientWatcher::setProjectId(const juce::String& id) 214 { 215 juce::Logger::writeToLog("Setting projectId"); 216 217 auto onCallAsync = [this, id = id] 218 { 219 projectId = id; 220 // On the first run of the extension, the project ID will be empty. On project load we 221 // also explicetly set the id to empty so that Project Support knows it has to refresh the project data. 222 // The juce value tree will not send updates if the property doesnt change. Lets force it to send an update. 223 applicationState.sendPropertyChangeMessage(IDs::projectId); 224 }; 225 226 juce::MessageManager::callAsync(onCallAsync); 227 } 228 229 void WaapiClientWatcher::setWwiseObjectsChanged(bool changed) 230 { 231 juce::Logger::writeToLog("Setting wwise objects changed"); 232 233 auto onCallAsync = [this, changed = changed] 234 { 235 wwiseObjectsChanged = changed; 236 }; 237 238 juce::MessageManager::callAsync(onCallAsync); 239 } 240 241 void WaapiClientWatcher::disconnectFromWaapi() 242 { 243 using namespace WwiseAuthoringAPI; 244 AkJson result; 245 if(waapiClient.unsubscribe(projectLoadedSubscriptionId, result)) 246 { 247 juce::Logger::writeToLog("Unsubscribed from project loaded"); 248 } 249 else 250 { 251 juce::Logger::writeToLog("Failed to unsubscribed from project unloaded"); 252 } 253 254 if(waapiClient.unsubscribe(projectPostClosedSubscriptionId, result)) 255 { 256 juce::Logger::writeToLog("Unsubscribed from project post closed"); 257 } 258 else 259 { 260 juce::Logger::writeToLog("Failed to unsubscribed from project post closed"); 261 } 262 263 if(waapiClient.unsubscribe(objectCreatedEventSubscriptionId, result)) 264 { 265 juce::Logger::writeToLog("Unsubscribed from object created"); 266 } 267 else 268 { 269 juce::Logger::writeToLog("Failed to unsubscribed from object created"); 270 } 271 272 if(waapiClient.unsubscribe(objectPostDeletedEventSubscriptionId, result)) 273 { 274 juce::Logger::writeToLog("Unsubscribed from object postDeleted"); 275 } 276 else 277 { 278 juce::Logger::writeToLog("Failed to unsubscribed from object postDeleted"); 279 } 280 281 if(waapiClient.unsubscribe(objectNameChangedEventSubscriptionId, result)) 282 { 283 juce::Logger::writeToLog("Unsubscribed from object postDeleted"); 284 } 285 else 286 { 287 juce::Logger::writeToLog("Failed to unsubscribed from object postDeleted"); 288 } 289 290 waapiClient.disconnect(); 291 292 setWaapiConnected(false); 293 setProjectId(""); 294 } 295 296 void WaapiClientWatcher::setWaapiConnected(bool connected) 297 { 298 juce::Logger::writeToLog("Setting waapi connected"); 299 300 auto resetProjectInfo = [this, connected = connected] 301 { 302 waapiConnected = connected; 303 }; 304 305 juce::MessageManager::callAsync(resetProjectInfo); 306 } 307 308 WaapiClient::WaapiClient() 309 { 310 } 311 312 bool WaapiClient::connect(const char* in_uri, unsigned int in_port, WwiseAuthoringAPI::disconnectHandler_t disconnectHandler, int in_timeoutMs) 313 { 314 return Connect(in_uri, in_port, disconnectHandler, in_timeoutMs); 315 } 316 317 bool WaapiClient::subscribe(const char* in_uri, const WwiseAuthoringAPI::AkJson& in_options, WampEventCallback in_callback, uint64_t& out_subscriptionId, WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) 318 { 319 return Subscribe(in_uri, in_options, in_callback, out_subscriptionId, out_result, in_timeoutMs); 320 } 321 322 bool WaapiClient::unsubscribe(const uint64_t& in_subscriptionId, WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) 323 { 324 return Unsubscribe(in_subscriptionId, out_result, in_timeoutMs); 325 } 326 327 bool WaapiClient::isConnected() const 328 { 329 return IsConnected(); 330 } 331 332 void WaapiClient::disconnect() 333 { 334 Disconnect(); 335 } 336 337 bool WaapiClient::call(const char* in_uri, const WwiseAuthoringAPI::AkJson& in_args, const WwiseAuthoringAPI::AkJson& in_options, WwiseAuthoringAPI::AkJson& out_result, int in_timeoutMs) 338 { 339 using namespace WwiseAuthoringAPI; 340 341 auto status = Call(in_uri, in_args, in_options, out_result, in_timeoutMs); 342 343 juce::Logger::writeToLog(juce::String(in_uri) + 344 juce::NewLine() + juce::String("args: ") + JSONHelpers::GetAkJsonString(in_args) + 345 juce::NewLine() + juce::String("options: ") + JSONHelpers::GetAkJsonString(in_options) + 346 juce::NewLine() + juce::String("result: ") + JSONHelpers::GetAkJsonString(out_result)); 347 348 return status; 349 } 350 351 bool WaapiClient::call(const char* in_uri, const char* in_args, const char* in_options, std::string& out_result, int in_timeoutMs) 352 { 353 auto status = Call(in_uri, in_args, in_options, out_result, in_timeoutMs); 354 355 juce::Logger::writeToLog(juce::String(in_uri) + 356 juce::NewLine() + juce::String("args: ") + in_args + 357 juce::NewLine() + juce::String("options: ") + in_options + 358 juce::NewLine() + juce::String("result: ") + out_result); 359 360 return status; 361 } 362 363 Waapi::Response<Waapi::ObjectResponseSet> WaapiClient::import(const std::vector<Waapi::ImportItemRequest>& importItemsRequest, Import::ContainerNameExistsOption containerNameExistsOption, const juce::String& objectLanguage) 364 { 365 using namespace WwiseAuthoringAPI; 366 367 Waapi::Response<Waapi::ObjectResponseSet> response; 368 369 AkJson::Array importItemsAsJson; 370 for(const auto& importItemRequest : importItemsRequest) 371 { 372 std::string key; 373 std::string value; 374 if(importItemRequest.renderFileWavBase64.isEmpty()) 375 { 376 key = "audioFile"; 377 value = importItemRequest.renderFilePath.toStdString(); 378 } 379 else 380 { 381 key = "audioFileBase64"; 382 value = importItemRequest.renderFileName.toStdString() + "|" + importItemRequest.renderFileWavBase64.toStdString(); 383 } 384 using namespace juce; 385 auto importItemAsJson = AkJson(AkJson::Map{ 386 {key, 387 AkVariant(value)}, 388 { 389 "objectPath", 390 AkVariant(importItemRequest.path.toStdString()), 391 }, 392 { 393 "originalsSubFolder", 394 AkVariant(importItemRequest.originalsSubFolder.toStdString()), 395 }, 396 }); 397 398 importItemsAsJson.push_back(importItemAsJson); 399 }; 400 401 const auto args = AkJson::Map{ 402 { 403 "importOperation", 404 AkVariant(ImportHelper::containerNameExistsOptionToString(containerNameExistsOption).toStdString()), 405 }, 406 { 407 "default", 408 AkJson::Map{{"importLanguage", AkVariant(objectLanguage.toStdString())}}, 409 }, 410 { 411 "imports", 412 importItemsAsJson, 413 }, 414 { 415 "autoAddToSourceControl", 416 AkVariant(true), 417 }, 418 }; 419 420 static const auto options = AkJson::Map{ 421 { 422 "return", 423 AkJson::Array{ 424 AkVariant{"id"}, 425 AkVariant{"name"}, 426 AkVariant{"type"}, 427 AkVariant{"path"}, 428 AkVariant{"sound:originalWavFilePath"}, 429 }, 430 }, 431 }; 432 433 AkJson result; 434 response.status = call(WaapiCommands::audioImport, args, options, result); 435 436 if(response.status) 437 { 438 if(result.HasKey("objects")) 439 { 440 auto objects = result["objects"].GetArray(); 441 442 for(auto& object : objects) 443 { 444 response.result.emplace(object); 445 } 446 } 447 } 448 else 449 { 450 response.error = WaapiHelper::parseError(WaapiCommands::audioImport, result); 451 } 452 453 return response; 454 } 455 456 bool WaapiClient::selectObjects(const juce::String& selectObjectsOnImportCommand, const std::vector<juce::String>& objectPaths) 457 { 458 using namespace WwiseAuthoringAPI; 459 460 AkJson::Array objects; 461 for(const auto& objectPath : objectPaths) 462 { 463 objects.emplace_back(AkVariant(objectPath.toStdString())); 464 } 465 466 const auto args = AkJson::Map{ 467 { 468 "command", 469 AkVariant(selectObjectsOnImportCommand.toStdString()), 470 }, 471 { 472 "objects", 473 objects, 474 }, 475 }; 476 477 AkJson result; 478 return call(WaapiCommands::commandsExecute, args, AkJson::Map{}, result); 479 } 480 481 Waapi::Response<Waapi::ObjectResponseSet> WaapiClient::getObjectAncestorsAndDescendants(const juce::String& objectPath) 482 { 483 using namespace WwiseAuthoringAPI; 484 485 Waapi::Response<Waapi::ObjectResponseSet> response; 486 487 auto waql = juce::String("\"" + objectPath + "\" select this, ancestors, descendants"); 488 489 const auto args = AkJson::Map{ 490 { 491 "waql", 492 AkVariant{waql.toStdString()}, 493 }, 494 }; 495 496 static const auto options = AkJson::Map{ 497 { 498 "return", 499 AkJson::Array{ 500 AkVariant{"id"}, 501 AkVariant{"name"}, 502 AkVariant{"type"}, 503 AkVariant{"path"}, 504 AkVariant{"sound:originalWavFilePath"}, 505 AkVariant{"workunitType"}, 506 }, 507 }, 508 }; 509 510 AkJson result; 511 response.status = call(WaapiCommands::objectGet, args, options, result); 512 513 if(!response.status) 514 { 515 // The waql query above will fail if the object was not found. 516 // We still want to know if ancestors exist. 517 auto objectAncestors = WwiseHelper::pathToAncestorPaths(objectPath); 518 519 for(int i = objectAncestors.size() - 1; i >= 0; --i) 520 { 521 auto waql = juce::String("\"" + objectAncestors[i] + "\" select this, ancestors"); 522 523 const auto args = AkJson::Map{ 524 { 525 "waql", 526 AkVariant{waql.toStdString()}, 527 }, 528 }; 529 530 response.status = call(WaapiCommands::objectGet, args, options, result); 531 532 if(response.status) 533 break; 534 } 535 } 536 537 if(response.status) 538 { 539 if(result.HasKey("return")) 540 { 541 auto objects = result["return"].GetArray(); 542 543 for(auto& object : objects) 544 { 545 response.result.emplace(object); 546 } 547 } 548 } 549 else 550 { 551 if(result.HasKey("message")) 552 { 553 juce::String message(result["message"].GetVariant().GetString()); 554 if(message.contains(WaapiMessages::objectNotFound)) 555 response.status = true; 556 } 557 558 response.error = WaapiHelper::parseError(WaapiCommands::objectGet, result); 559 } 560 561 return response; 562 } 563 564 Waapi::Response<std::vector<juce::String>> WaapiClient::getProjectLanguages() 565 { 566 using namespace WwiseAuthoringAPI; 567 568 Waapi::Response<std::vector<juce::String>> response; 569 570 static const auto args(AkJson::Map{ 571 { 572 "from", 573 AkJson::Map{ 574 { 575 "ofType", 576 AkJson::Array{ 577 {AkVariant("Language")}, 578 }, 579 }, 580 }, 581 }, 582 }); 583 584 static const auto options(AkJson::Map{ 585 { 586 "return", 587 AkJson::Array{ 588 {AkVariant("name")}, 589 }, 590 }, 591 }); 592 593 AkJson result; 594 response.status = call(WaapiCommands::objectGet, args, options, result); 595 596 static const std::set<juce::String> exceptions = { 597 "Mixed", 598 "SFX", 599 "External", 600 "SoundSeed Grain", 601 }; 602 603 if(response.status) 604 { 605 if(result.HasKey("return")) 606 { 607 auto objects = result["return"].GetArray(); 608 609 for(auto& object : objects) 610 { 611 auto language = juce::String(object["name"].GetVariant().GetString()); 612 613 if(exceptions.find(language) == exceptions.end()) 614 response.result.emplace_back(language); 615 } 616 } 617 } 618 else 619 { 620 response.error = WaapiHelper::parseError(WaapiCommands::objectGet, result); 621 } 622 623 return response; 624 } 625 626 Waapi::Response<Waapi::ObjectResponseSet> WaapiClient::getObjectAncestorsAndDescendantsLegacy(const juce::String& objectPath) 627 { 628 using namespace WwiseAuthoringAPI; 629 630 Waapi::Response<Waapi::ObjectResponseSet> response; 631 632 static auto buildArgs = [](const juce::String& path) 633 { 634 auto from = AkJson::Map{ 635 { 636 "from", 637 AkJson::Map{ 638 { 639 "path", 640 AkJson::Array{ 641 AkVariant{path.toStdString()}, 642 }, 643 }, 644 }, 645 }, 646 }; 647 648 return from; 649 }; 650 651 static auto addTransform = [](AkJson::Map& args, const juce::String& transform) 652 { 653 args["transform"] = AkJson::Array{ 654 AkJson::Map{ 655 { 656 "select", 657 AkJson::Array{ 658 AkVariant{transform.toStdString()}, 659 }, 660 }, 661 }, 662 }; 663 }; 664 665 static auto fillResponse = [](auto& response, const auto& result) 666 { 667 if(result.HasKey("return")) 668 { 669 auto objects = result["return"].GetArray(); 670 671 for(auto& object : objects) 672 { 673 response.result.emplace(object); 674 } 675 } 676 }; 677 678 static const auto options = AkJson::Map{ 679 { 680 "return", 681 AkJson::Array{ 682 AkVariant{"id"}, 683 AkVariant{"name"}, 684 AkVariant{"type"}, 685 AkVariant{"path"}, 686 AkVariant{"sound:originalWavFilePath"}, 687 AkVariant{"workunitType"}, 688 }, 689 }, 690 }; 691 692 auto args = buildArgs(objectPath); 693 694 AkJson result; 695 response.status = call(WaapiCommands::objectGet, args, options, result); 696 697 if(response.status) 698 fillResponse(response, result); 699 700 args = buildArgs(objectPath); 701 addTransform(args, "descendants"); 702 703 response.status = call(WaapiCommands::objectGet, args, options, result); 704 705 if(response.status) 706 fillResponse(response, result); 707 708 args = buildArgs(objectPath); 709 addTransform(args, "ancestors"); 710 711 response.status = call(WaapiCommands::objectGet, args, options, result); 712 713 if(response.status) 714 fillResponse(response, result); 715 else 716 { 717 auto objectAncestors = WwiseHelper::pathToAncestorPaths(objectPath); 718 719 for(int i = objectAncestors.size() - 1; i >= 0; --i) 720 { 721 args = buildArgs(objectAncestors[i]); 722 723 response.status = call(WaapiCommands::objectGet, args, options, result); 724 725 if(response.status) 726 { 727 fillResponse(response, result); 728 729 args = buildArgs(objectAncestors[i]); 730 addTransform(args, "ancestors"); 731 732 response.status = call(WaapiCommands::objectGet, args, options, result); 733 734 if(response.status) 735 { 736 fillResponse(response, result); 737 break; 738 } 739 } 740 } 741 } 742 743 if(!response.status) 744 response.error = WaapiHelper::parseError(WaapiCommands::objectGet, result); 745 746 return response; 747 } 748 749 Waapi::Response<Waapi::ObjectResponse> WaapiClient::getObject(const juce::String& objectPath) 750 { 751 using namespace WwiseAuthoringAPI; 752 753 Waapi::Response<Waapi::ObjectResponse> response; 754 755 const auto args = AkJson::Map{ 756 { 757 "from", 758 AkJson::Map{ 759 { 760 "path", 761 AkJson::Array{ 762 AkVariant{objectPath.toStdString()}, 763 }, 764 }, 765 }, 766 }, 767 }; 768 769 static const auto options = AkJson::Map{ 770 { 771 "return", 772 AkJson::Array{ 773 AkVariant{"id"}, 774 AkVariant{"name"}, 775 AkVariant{"type"}, 776 AkVariant{"path"}, 777 AkVariant{"sound:originalWavFilePath"}, 778 AkVariant{"workunitType"}, 779 }, 780 }, 781 }; 782 783 AkJson result; 784 response.status = call(WaapiCommands::objectGet, args, options, result); 785 786 if(response.status) 787 { 788 if(result.HasKey("return")) 789 { 790 auto objects = result["return"].GetArray(); 791 792 for(auto& object : objects) 793 { 794 response.result = object; 795 } 796 } 797 } 798 // Special Case: The call actually succeeds but does not find the object 799 else if(result.HasKey("uri") && result["uri"].GetVariant().GetString() == WaapiURIs::unknownObject) 800 { 801 response.status = true; 802 } 803 else 804 { 805 response.error = WaapiHelper::parseError(WaapiCommands::objectGet, result); 806 } 807 808 return response; 809 } 810 811 void WaapiClient::beginUndoGroup() 812 { 813 using namespace WwiseAuthoringAPI; 814 815 AkJson result; 816 call(WaapiCommands::undoBeginGroup, AkJson::Map{}, AkJson::Map{}, result); 817 } 818 819 void WaapiClient::cancelUndoGroup() 820 { 821 using namespace WwiseAuthoringAPI; 822 823 AkJson result; 824 call(WaapiCommands::undoCancelGroup, AkJson::Map{}, AkJson::Map{}, result); 825 } 826 827 void WaapiClient::endUndoGroup(const juce::String& displayName) 828 { 829 using namespace WwiseAuthoringAPI; 830 831 auto arguments = AkJson::Map{ 832 { 833 "displayName", 834 AkVariant{displayName.toUTF8()}, 835 }, 836 }; 837 838 AkJson result; 839 call(WaapiCommands::undoEndGroup, arguments, AkJson::Map{}, result); 840 } 841 842 Waapi::Response<Waapi::ObjectResponseSet> WaapiClient::pasteProperties(const Waapi::PastePropertiesRequest& pastePropertiesRequest) 843 { 844 using namespace WwiseAuthoringAPI; 845 846 Waapi::Response<Waapi::ObjectResponseSet> response; 847 848 AkJson::Array targets; 849 for(auto& target : pastePropertiesRequest.targets) 850 { 851 targets.push_back(AkVariant(target.toStdString())); 852 } 853 854 const auto args(AkJson::Map{ 855 { 856 "source", 857 AkVariant(pastePropertiesRequest.source.toStdString()), 858 }, 859 { 860 "targets", 861 targets, 862 }, 863 }); 864 865 AkJson result; 866 response.status = call(WaapiCommands::objectPasteProperties, args, AkJson::Map{}, result); 867 868 if(!response.status) 869 { 870 response.error = WaapiHelper::parseError(WaapiCommands::objectPasteProperties, result); 871 } 872 873 return response; 874 } 875 876 Waapi::Response<Wwise::Version> WaapiClient::getVersion() 877 { 878 using namespace WwiseAuthoringAPI; 879 880 Waapi::Response<Wwise::Version> response; 881 882 AkJson result; 883 response.status = call(WaapiCommands::getInfo, AkJson::Map{}, AkJson::Map{}, result); 884 885 if(response.status) 886 { 887 if(result.HasKey("version") && result["version"].HasKey("displayName")) 888 { 889 response.result = { 890 result["version"]["year"].GetVariant().GetInt32(), 891 result["version"]["major"].GetVariant().GetInt32(), 892 result["version"]["minor"].GetVariant().GetInt32(), 893 result["version"]["build"].GetVariant().GetInt32(), 894 }; 895 } 896 } 897 else 898 { 899 response.error = WaapiHelper::parseError(WaapiCommands::getInfo, result); 900 } 901 902 return response; 903 } 904 905 Waapi::Response<Waapi::ProjectInfo> WaapiClient::getProjectInfo() 906 { 907 using namespace WwiseAuthoringAPI; 908 909 Waapi::Response<Waapi::ProjectInfo> response; 910 911 static const auto args(AkJson::Map{ 912 { 913 "from", 914 AkJson::Map{ 915 { 916 "ofType", 917 AkJson::Array{ 918 {AkVariant("Project")}, 919 }, 920 }, 921 }, 922 }, 923 }); 924 925 static const auto options(AkJson::Map{ 926 { 927 "return", 928 AkJson::Array{ 929 {AkVariant("filePath")}, 930 {AkVariant("id")}, 931 }, 932 }, 933 }); 934 935 AkJson result; 936 response.status = call(WaapiCommands::objectGet, args, options, result); 937 938 if(response.status) 939 { 940 if(result.HasKey("return")) 941 { 942 const auto& returnResult = result["return"]; 943 944 if(!returnResult.GetArray().empty()) 945 { 946 auto projectPath = juce::String(returnResult[0]["filePath"].GetVariant().GetString()); 947 auto projectId = juce::String(returnResult[0]["id"].GetVariant().GetString()).removeCharacters("{}"); 948 949 #ifndef WIN32 950 // Waapi returns the path as a windows path 951 projectPath = projectPath.replace("\\", juce::File::getSeparatorString()).replace("Y:", "~").replace("Z:", "/"); 952 #endif 953 954 response.result.projectPath = projectPath; 955 response.result.projectId = projectId; 956 } 957 } 958 } 959 else 960 { 961 response.error = WaapiHelper::parseError(WaapiCommands::objectGet, result); 962 } 963 964 return response; 965 } 966 Waapi::Response<Waapi::AdditionalProjectInfo> WaapiClient::getAdditionalProjectInfo() 967 { 968 using namespace WwiseAuthoringAPI; 969 970 Waapi::Response<Waapi::AdditionalProjectInfo> response; 971 972 AkJson result; 973 response.status = call(WaapiCommands::getProjectInfo, AkJson::Map{}, AkJson::Map{}, result); 974 975 if(result.HasKey("directories") && result["directories"].HasKey("originals")) 976 { 977 auto originalsFolder = juce::String(result["directories"]["originals"].GetVariant().GetString()); 978 979 #ifndef WIN32 980 // Waapi returns the path as a windows path 981 originalsFolder = originalsFolder.replace("\\", juce::File::getSeparatorString()).replace("Y:", "~").replace("Z:", "/"); 982 #endif 983 984 response.result.originalsFolder = originalsFolder; 985 } 986 987 if(result.HasKey("referenceLanguageId") && result.HasKey("languages")) 988 { 989 const auto& referenceLanguageId = result["referenceLanguageId"].GetVariant().GetString(); 990 const auto& languages = result["languages"].GetArray(); 991 992 for(const auto& languageAsAkJson : languages) 993 { 994 const auto& language = languageAsAkJson.GetMap(); 995 const auto& langIdIt = language.find("id"); 996 const auto& langNameIt = language.find("name"); 997 998 if(langIdIt != language.cend() && langNameIt != language.cend()) 999 { 1000 if(langIdIt->second.GetVariant().GetString() == referenceLanguageId) 1001 { 1002 response.result.referenceLanguage = langNameIt->second.GetVariant().GetString(); 1003 break; 1004 } 1005 } 1006 } 1007 } 1008 1009 return response; 1010 } 1011 Waapi::Response<Waapi::ObjectResponse> WaapiClient::getSelectedObject() 1012 { 1013 using namespace WwiseAuthoringAPI; 1014 1015 Waapi::Response<Waapi::ObjectResponse> response; 1016 1017 static const auto options = AkJson::Map{ 1018 { 1019 "return", 1020 AkJson::Array{ 1021 AkVariant{"id"}, 1022 AkVariant{"name"}, 1023 AkVariant{"type"}, 1024 AkVariant{"path"}, 1025 }, 1026 }, 1027 }; 1028 1029 AkJson result; 1030 response.status = call(WaapiCommands::getSelectedObjects, AkJson::Map{}, options, result); 1031 1032 if(response.status) 1033 { 1034 if(result.HasKey("objects")) 1035 { 1036 const auto& returnObjects = result["objects"]; 1037 1038 if(!returnObjects.GetArray().empty()) 1039 response.result = Waapi::ObjectResponse(returnObjects.GetArray()[0]); 1040 } 1041 } 1042 else 1043 { 1044 response.error = WaapiHelper::parseError(WaapiCommands::getSelectedObjects, result); 1045 } 1046 1047 return response; 1048 } 1049 } // namespace AK::WwiseTransfer