commit 6397e72977b9315e06b91fd2cb0d87958b2dc92c
parent 208a34d1812a2e20541c5173c27a20b1d718f021
Author: Andrew Costa <acosta@audiokinetic.com>
Date: Tue, 20 Sep 2022 10:21:37 -0400
New Features:
* Added "ReaWwise: Open", "ReaWwise: Close" and "ReaWwise: Transfer To Wwise" actions to REAPER's action list. And added AK_Json_Clear and AK_Json_ClearAll functions to ReaScript.
Miscellaneous Changes:
* Made read-only text fields look different from editable ones.
* Added focus feedback for drop-down lists.
* Added more tooltips throughout the user interface.
* Added Actor-Mixer as a possible Object Type for Wwise structures.
Bug Fixes:
* Fixed: A corrupted preset file is created when clicking Cancel in the Wwise structures Save Preset dialog.
* Fixed: Transfer to Wwise button is still available when there are no items in preview pane.
* Fixed: Import details are wrong when Originals folder is blank.
* Fixed: When attempting to create a Sound SFX directly under a Physical Folder (an illegal parent/child relationship), no error is generated and the audio files are added to the Originals folder.
Change-Id: I0de0150ab48a7c84f6edb3782cd7cbfebbab7d86
Diffstat:
47 files changed, 1527 insertions(+), 643 deletions(-)
diff --git a/3rd/reaper-sdk/sdk/reaper_plugin_functions.h b/3rd/reaper-sdk/sdk/reaper_plugin_functions.h
@@ -2,7 +2,7 @@
#define _REAPER_PLUGIN_FUNCTIONS_H_
// REAPER API functions
-// Generated by REAPER v6.67+dev0914/win32
+// Generated by REAPER v6.68+dev0927/win64
/*
* Copyright 2006 and later, Cockos Incorporated
diff --git a/src/extension/CMakeLists.txt b/src/extension/CMakeLists.txt
@@ -15,11 +15,13 @@ if(WIN32)
else()
file(GLOB_RECURSE EXTENSION_SOURCES
"${PROJECT_SOURCE_DIR}/*.h"
- "${PROJECT_SOURCE_DIR}/*.cpp"
- "${PROJECT_SOURCE_DIR}/*.mm")
+ "${PROJECT_SOURCE_DIR}/*.cpp")
endif()
add_library(${PROJECT_NAME} SHARED)
+add_library(${PROJECT_NAME}_Static STATIC)
+
+set(PROJECT_LIST ${PROJECT_NAME} ${PROJECT_NAME}_Static)
if(NOT DEFINED ENV{BUILD_NUMBER})
if(APPLE)
@@ -42,19 +44,46 @@ if(NOT DEFINED ENV{BUILD_NUMBER})
endif()
endif()
-
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME reaper_reawwise)
-target_sources(${PROJECT_NAME} PRIVATE ${EXTENSION_SOURCES})
+foreach(PROJ ${PROJECT_LIST})
+ target_sources(${PROJ} PRIVATE ${EXTENSION_SOURCES})
-find_package(Reaper REQUIRED)
+ target_include_directories(${PROJ}
+ PUBLIC
+ ${PROJECT_SOURCE_DIR}/
+ )
-target_link_libraries(${PROJECT_NAME}
- PRIVATE
- WwiseTransfer_Shared
- Reaper
-)
+ target_link_libraries(${PROJ}
+ PRIVATE
+ WwiseTransfer_Shared
+ Reaper
+ )
+
+ target_compile_definitions(${PROJ}
+ PRIVATE
+ REAPERAPI_MINIMAL
+ REAPERAPI_WANT_GetMainHwnd
+ REAPERAPI_WANT_AddExtensionsMainMenu
+ REAPERAPI_WANT_EnumProjects
+ REAPERAPI_WANT_GetSetProjectInfo_String
+ REAPERAPI_WANT_ResolveRenderPattern
+ REAPERAPI_WANT_Main_OnCommand
+ REAPERAPI_WANT_GetProjExtState
+ REAPERAPI_WANT_SetProjExtState
+ REAPERAPI_WANT_MarkProjectDirty
+ REAPERAPI_WANT_realloc_cmd_register_buf
+ REAPERAPI_WANT_realloc_cmd_clear
+ REAPERAPI_WANT_GetProjectStateChangeCount
+ REAPERAPI_WANT_GetSetProjectInfo
+ JUCE_APPLICATION_NAME_STRING="${PROJECT_NAME}"
+ JUCE_STANDALONE_APPLICATION=0
+ JUCE_REMOVE_COMPONENT_FROM_DESKTOP_ON_WM_DESTROY=1
+ )
+endforeach()
+
+find_package(Reaper REQUIRED)
if(APPLE)
if(DEFINED ENV{CODE_SIGN_IDENTITY_ID} AND DEFINED ENV{DEVELOPMENT_TEAM_ID})
@@ -64,32 +93,11 @@ if(APPLE)
find_package(Swell REQUIRED)
- target_link_libraries(${PROJECT_NAME}
- PRIVATE
- Swell
- )
+ foreach(PROJ ${PROJECT_LIST})
+ target_link_libraries(${PROJ} PRIVATE Swell)
+ endforeach()
endif()
-target_compile_definitions(${PROJECT_NAME}
- PRIVATE
- REAPERAPI_MINIMAL
- REAPERAPI_WANT_GetMainHwnd
- REAPERAPI_WANT_AddExtensionsMainMenu
- REAPERAPI_WANT_EnumProjects
- REAPERAPI_WANT_GetSetProjectInfo_String
- REAPERAPI_WANT_ResolveRenderPattern
- REAPERAPI_WANT_Main_OnCommand
- REAPERAPI_WANT_GetProjExtState
- REAPERAPI_WANT_SetProjExtState
- REAPERAPI_WANT_MarkProjectDirty
- REAPERAPI_WANT_realloc_cmd_register_buf
- REAPERAPI_WANT_realloc_cmd_clear
- REAPERAPI_WANT_GetProjectStateChangeCount
- REAPERAPI_WANT_GetSetProjectInfo
- JUCE_APPLICATION_NAME_STRING="${PROJECT_NAME}"
- JUCE_STANDALONE_APPLICATION=0
-)
-
source_group(TREE ${PROJECT_SOURCE_DIR} PREFIX "Source Files" FILES ${EXTENSION_SOURCES})
build_juce_source_groups()
diff --git a/src/extension/Extension.cpp b/src/extension/Extension.cpp
@@ -1,9 +1,10 @@
#define REAPERAPI_IMPLEMENT
#include "Core/WaapiClient.h"
-#include "ExtensionWindow.h"
#include "ReaperContext.h"
+#include "ReaperPlugin.h"
#include "Theme/CustomLookAndFeel.h"
+#include "UI/MainWindow.h"
#include <JSONHelpers.h>
#include <juce_events/juce_events.h>
@@ -12,18 +13,12 @@
#include <tuple>
#include <variant>
-#ifdef __APPLE__
-#include "MacHelpers.h"
-#endif
-
namespace AK::ReaWwise
{
- static bool juceInitialised = false;
- static constexpr int defaultBufferSize = 4096;
- static constexpr int largeBufferSize = 4 * 1024 * 1024;
- static std::unique_ptr<ExtensionWindow> mainWindow;
+ static bool guiInitialised = false;
+ static std::unique_ptr<WwiseTransfer::MainWindow> mainWindow;
static std::unique_ptr<ReaperContext> reaperContext;
- static std::unique_ptr<ReaperPluginInterface> reaperPluginInterface;
+ static std::unique_ptr<IReaperPlugin> reaperPlugin;
static std::string returnString;
static std::string emptyReturnString;
static double returnDouble;
@@ -42,28 +37,32 @@ namespace AK::ReaWwise
}
}
- static bool onHookCommand(int command, int flag)
+ static void initialiseGui()
{
- if(command == openReaperWwiseTransferCommandId)
- {
- if(!juceInitialised)
- {
- juce::initialiseJuce_GUI();
- juceInitialised = true;
- }
+ juce::initialiseJuce_GUI();
+ guiInitialised = true;
+ }
- if(!mainWindow)
- {
- mainWindow = std::make_unique<ExtensionWindow>(*reaperContext);
+ static void initialiseMainWindow()
+ {
+ mainWindow = std::make_unique<WwiseTransfer::MainWindow>(*reaperContext, JUCE_APPLICATION_NAME_STRING, false);
#ifdef WIN32
- mainWindow->addToDesktop(mainWindow->getDesktopWindowStyleFlags(), reaperPluginInterface->getMainHwnd());
+ mainWindow->addToDesktop(mainWindow->getDesktopWindowStyleFlags(), reaperPlugin->getMainHwnd());
#else
- mainWindow->addToDesktop(mainWindow->getDesktopWindowStyleFlags(), 0);
- // MacHelpers::makeWindowFloatingPanel(dynamic_cast<juce::Component*>(mainWindow.get()));
+ mainWindow->addToDesktop(mainWindow->getDesktopWindowStyleFlags(), 0);
#endif
- }
+ }
+
+ static bool onHookCommand(int command, int flag)
+ {
+ if(command == openReaperWwiseTransferCommandId)
+ {
+ if(!guiInitialised)
+ initialiseGui();
+
+ if(mainWindow == nullptr)
+ initialiseMainWindow();
- // TODO: There might be more stuff that needs to be done on visibility toggle, i.e. re-read app properties, keeping the extension alive
mainWindow->setVisible(!mainWindow->isVisible());
return true;
@@ -654,6 +653,74 @@ namespace AK::ReaWwise
return (void*)AkJson_GetStatus(arguments.get<0>());
}
+ static const bool AkJson_Clear(std::shared_ptr<AkJsonRef>* akJsonRef)
+ {
+ if(isObjectValid(akJsonRef, objects))
+ return objects.erase(*akJsonRef) == 1;
+
+ return false;
+ }
+
+ static void* AkJson_ClearVarArg(void** argv, int argc)
+ {
+ juce::ignoreUnused(argc);
+
+ Arguments<std::shared_ptr<AkJsonRef>*> arguments(argv);
+
+ return (void*)AkJson_Clear(arguments.get<0>());
+ }
+
+ static const bool AkJson_ClearAll()
+ {
+ objects.clear();
+
+ return objects.size() == 0;
+ }
+
+ static void* AkJson_ClearAllVarArg(void** argv, int argc)
+ {
+ juce::ignoreUnused(argv, argc);
+
+ return (void*)AkJson_ClearAll();
+ }
+
+ static void ReaWwise_Open()
+ {
+ if(!guiInitialised)
+ initialiseGui();
+
+ if(mainWindow == nullptr)
+ initialiseMainWindow();
+
+ if(!mainWindow->isVisible())
+ mainWindow->setVisible(true);
+ }
+
+ static void ReaWwise_Close()
+ {
+ if(mainWindow && mainWindow->isVisible())
+ mainWindow->setVisible(false);
+ }
+
+ static void ReaWwise_TransferToWwise()
+ {
+ if(mainWindow)
+ mainWindow->transferToWwise();
+ }
+
+ struct ApiAction
+ {
+ int id;
+ custom_action_register_t definition;
+ void (*function)();
+ };
+
+ static ApiAction apiActions[] = {
+ ApiAction{0, {0, "AK_ReaWwise_Open", "ReaWwise: Open", nullptr}, &ReaWwise_Open},
+ ApiAction{0, {0, "AK_ReaWwise_Close", "ReaWwise: Close", nullptr}, &ReaWwise_Close},
+ ApiAction{0, {0, "AK_ReaWwise_TransferToWwise", "ReaWwise: Transfer To Wwise (Using current settings)", nullptr}, &ReaWwise_TransferToWwise},
+ };
+
struct ApiFunctionDefinition
{
const char* Api;
@@ -691,6 +758,9 @@ namespace AK::ReaWwise
AK_RWT_GENERATE_API_FUNC_DEF(AkJson_GetStatus, "bool", "*", "*", "Ak: Get the status of a result from a call to waapi"),
+ AK_RWT_GENERATE_API_FUNC_DEF(AkJson_Clear, "bool", "*", "*", "Ak: Clear object referenced by pointer"),
+ AK_RWT_GENERATE_API_FUNC_DEF(AkJson_ClearAll, "bool", "", "", "Ak: Clear all objects rederenced by pointers"),
+
AK_RWT_GENERATE_API_FUNC_DEF(AkVariant_Bool, "*", "bool", "bool", "Ak: Create a bool object"),
AK_RWT_GENERATE_API_FUNC_DEF(AkVariant_GetBool, "bool", "*", "*", "Ak: Extract raw boolean value from bool object"),
@@ -707,195 +777,70 @@ namespace AK::ReaWwise
#undef AK_RWT_GENERATE_API_FUNC_DEF
} // namespace Scripting
- static int initialize(reaper_plugin_info_t* pluginInfo)
+ static bool onHookCommand2(KbdSectionInfo* sec, int command, int val, int val2, int relmode, HWND hwnd)
{
- class ReaperPluginImplementation : public ReaperPluginInterface
+ for(const auto& apiAction : Scripting::apiActions)
{
- public:
- ReaperPluginImplementation(reaper_plugin_info_t* pluginInfo)
- : pluginInfo(pluginInfo)
- {
- _getMainHwnd = decltype(GetMainHwnd)(pluginInfo->GetFunc("GetMainHwnd"));
- _addExtensionsMainMenu = decltype(AddExtensionsMainMenu)(pluginInfo->GetFunc("AddExtensionsMainMenu"));
- _enumProjects = decltype(EnumProjects)(pluginInfo->GetFunc("EnumProjects"));
- _getSetProjectInfo_String = decltype(GetSetProjectInfo_String)(pluginInfo->GetFunc("GetSetProjectInfo_String"));
- _resolveRenderPattern = decltype(ResolveRenderPattern)(pluginInfo->GetFunc("ResolveRenderPattern"));
- _main_OnCommand = decltype(Main_OnCommand)(pluginInfo->GetFunc("Main_OnCommand"));
- _getProjExtState = decltype(GetProjExtState)(pluginInfo->GetFunc("GetProjExtState"));
- _setProjExtState = decltype(SetProjExtState)(pluginInfo->GetFunc("SetProjExtState"));
- _markProjectDirty = decltype(MarkProjectDirty)(pluginInfo->GetFunc("MarkProjectDirty"));
- _getProjectStateChangeCount = decltype(GetProjectStateChangeCount)(pluginInfo->GetFunc("GetProjectStateChangeCount"));
- _getSetProjectInfo = decltype(GetSetProjectInfo)(pluginInfo->GetFunc("GetSetProjectInfo"));
- _realloc_cmd_register_buf = decltype(realloc_cmd_register_buf)(pluginInfo->GetFunc("realloc_cmd_register_buf"));
- _realloc_cmd_clear = decltype(realloc_cmd_clear)(pluginInfo->GetFunc("realloc_cmd_clear"));
- }
-
- ~ReaperPluginImplementation() override = default;
-
- int getCallerVersion() const override
- {
- return pluginInfo->caller_version;
- }
-
- int registerFunction(const char* name, void* infoStruct) const override
- {
- return pluginInfo->Register(name, infoStruct);
- }
-
- bool isValid() const override
- {
- if(_getMainHwnd &&
- _addExtensionsMainMenu &&
- _enumProjects &&
- _getSetProjectInfo_String &&
- _resolveRenderPattern &&
- _main_OnCommand &&
- _getProjExtState &&
- _setProjExtState &&
- _markProjectDirty &&
- _getProjectStateChangeCount &&
- _getSetProjectInfo)
- return true;
-
- return false;
- }
-
- void* getMainHwnd() override
- {
- return _getMainHwnd();
- }
-
- bool addExtensionsMainMenu() override
- {
- return _addExtensionsMainMenu();
- }
-
- ReaProject* enumProjects(int idx, char* projfnOutOptional, int projfnOutOptional_sz) override
- {
- return _enumProjects(idx, projfnOutOptional, projfnOutOptional_sz);
- }
-
- juce::String getProjectString(ReaProject* proj, const char* key) override
- {
- juce::String projectString;
-
- if (realloc_cmd_register_buf && realloc_cmd_clear)
- {
- // For REAPER 6.68+
- char buffer[defaultBufferSize];
- char* bufferPtr = buffer;
-
- int bufferSize = (int)sizeof(buffer);
-
- int token = realloc_cmd_register_buf(&bufferPtr, &bufferSize);
-
- if (_getSetProjectInfo_String(proj, key, bufferPtr, false))
- projectString = juce::String(bufferPtr, bufferSize);
-
- realloc_cmd_clear(token);
- }
- else
- {
- static std::string buffer(largeBufferSize, '\0');
-
- if (_getSetProjectInfo_String(proj, key, &buffer[0], false))
- projectString = buffer;
- }
-
- return projectString;
- }
-
- int resolveRenderPattern(ReaProject* project, const char* path, const char* pattern, char* targets, int targets_sz) override
- {
- return _resolveRenderPattern(project, path, pattern, targets, targets_sz);
- }
-
- void main_OnCommand(int command, int flag) override
- {
- _main_OnCommand(command, flag);
- }
-
- int getProjExtState(ReaProject* proj, const char* extname, const char* key, char* valOutNeedBig, int valOutNeedBig_sz) override
+ if(apiAction.id == command)
{
- return _getProjExtState(proj, extname, key, valOutNeedBig, valOutNeedBig_sz);
- }
-
- int setProjExtState(ReaProject* proj, const char* extname, const char* key, const char* value) override
- {
- return _setProjExtState(proj, extname, key, value);
- }
-
- void markProjectDirty(ReaProject* proj) override
- {
- return _markProjectDirty(proj);
- }
-
- int getProjectStateChangeCount(ReaProject* proj) override
- {
- return _getProjectStateChangeCount(proj);
- }
-
- double getSetProjectInfo(ReaProject* proj, const char* desc, double value, bool is_set) override
- {
- return _getSetProjectInfo(proj, desc, value, is_set);
+ apiAction.function();
+ return true;
}
+ }
- private:
- reaper_plugin_info_t* pluginInfo;
-
- decltype(GetMainHwnd) _getMainHwnd;
- decltype(AddExtensionsMainMenu) _addExtensionsMainMenu;
- decltype(EnumProjects) _enumProjects;
- decltype(GetSetProjectInfo_String) _getSetProjectInfo_String;
- decltype(ResolveRenderPattern) _resolveRenderPattern;
- decltype(Main_OnCommand) _main_OnCommand;
- decltype(GetProjExtState) _getProjExtState;
- decltype(SetProjExtState) _setProjExtState;
- decltype(MarkProjectDirty) _markProjectDirty;
- decltype(GetProjectStateChangeCount) _getProjectStateChangeCount;
- decltype(GetSetProjectInfo) _getSetProjectInfo;
- decltype(realloc_cmd_register_buf) _realloc_cmd_register_buf;
- decltype(realloc_cmd_clear) _realloc_cmd_clear;
- };
+ return false;
+ }
- reaperPluginInterface = std::make_unique<ReaperPluginImplementation>(pluginInfo);
- reaperContext = std::make_unique<ReaperContext>(*reaperPluginInterface);
+ static int initialize(reaper_plugin_info_t* pluginInfo)
+ {
+ reaperPlugin = std::make_unique<ReaperPlugin>(pluginInfo);
+ reaperContext = std::make_unique<ReaperContext>(*reaperPlugin);
// Should actually report errors to the user somehow
- if(reaperPluginInterface->getCallerVersion() != REAPER_PLUGIN_VERSION)
+ if(reaperPlugin->getCallerVersion() != REAPER_PLUGIN_VERSION)
{
return 0;
}
// Checks that all function pointers needed from reaper are valid
- if(!reaperPluginInterface->isValid())
+ if(!reaperPlugin->isValid())
{
return 0;
}
- openReaperWwiseTransferCommandId = reaperPluginInterface->registerFunction("command_id", (void*)"openReaperWwiseTransferCommand");
+ openReaperWwiseTransferCommandId = reaperPlugin->registerFunction("command_id", (void*)"openReaperWwiseTransferCommand");
if(!openReaperWwiseTransferCommandId)
{
return 0;
}
- if(!reaperPluginInterface->registerFunction("hookcommand", (void*)onHookCommand))
+ if(!reaperPlugin->registerFunction("hookcommand2", (void*)onHookCommand2))
+ {
+ return 0;
+ }
+
+ if(!reaperPlugin->registerFunction("hookcommand", (void*)onHookCommand))
{
return 0;
}
- if(!reaperPluginInterface->registerFunction("hookcustommenu", (void*)onHookCustomMenu))
+ if(!reaperPlugin->registerFunction("hookcustommenu", (void*)onHookCustomMenu))
{
return 0;
}
- reaperPluginInterface->addExtensionsMainMenu();
+ reaperPlugin->addExtensionsMainMenu();
for(const auto& apiFunctionDefinition : Scripting::apiFunctionDefinitions)
{
- reaperPluginInterface->registerFunction(apiFunctionDefinition.Api, (void*)apiFunctionDefinition.FunctionPointer);
- reaperPluginInterface->registerFunction(apiFunctionDefinition.ApiVarArg, (void*)apiFunctionDefinition.FunctionPointerVarArg);
- reaperPluginInterface->registerFunction(apiFunctionDefinition.ApiDef, (void*)apiFunctionDefinition.FunctionSignature);
+ reaperPlugin->registerFunction(apiFunctionDefinition.Api, (void*)apiFunctionDefinition.FunctionPointer);
+ reaperPlugin->registerFunction(apiFunctionDefinition.ApiVarArg, (void*)apiFunctionDefinition.FunctionPointerVarArg);
+ reaperPlugin->registerFunction(apiFunctionDefinition.ApiDef, (void*)apiFunctionDefinition.FunctionSignature);
+ }
+
+ for(auto& apiAction : Scripting::apiActions)
+ {
+ apiAction.id = reaperPlugin->registerFunction("custom_action", (void*)&apiAction.definition);
}
return 1;
@@ -903,13 +848,25 @@ namespace AK::ReaWwise
static int cleanup()
{
- if(juceInitialised)
+ if(mainWindow != nullptr)
{
+ mainWindow->removeFromDesktop();
mainWindow.reset(nullptr);
+ }
+
+ if(reaperContext != nullptr)
reaperContext.reset(nullptr);
+ if(Scripting::waapiClient != nullptr)
+ Scripting::waapiClient.reset(nullptr);
+
+ if(Scripting::objects.size() > 0)
+ Scripting::objects.clear();
+
+ if(guiInitialised)
+ {
juce::shutdownJuce_GUI();
- juceInitialised = false;
+ guiInitialised = false;
}
return 0;
diff --git a/src/extension/ExtensionWindow.cpp b/src/extension/ExtensionWindow.cpp
@@ -1,62 +0,0 @@
-#include "ExtensionWindow.h"
-#include "UI/MainComponent.h"
-
-#include <limits>
-
-namespace AK::ReaWwise
-{
- namespace ExtensionWindowConstants
- {
- constexpr int width = 600;
- constexpr int height = 800;
- constexpr int minWidth = 420;
- constexpr int minHeight = 650;
- constexpr int standardDPI = 96;
- } // namespace ExtensionWindowConstants
-
- ExtensionWindow::ExtensionWindow(WwiseTransfer::DawContext& dawContext)
- : juce::ResizableWindow(JUCE_APPLICATION_NAME_STRING, false)
- {
- using namespace ExtensionWindowConstants;
-
- juce::LookAndFeel::setDefaultLookAndFeel(&lookAndFeel);
-
- auto mainContentComponent = new WwiseTransfer::MainComponent(dawContext, JUCE_APPLICATION_NAME_STRING);
-
-#ifdef WIN32
- if(!mainContentComponent->hasScaleFactorOverride())
- {
- auto scaleFactor = juce::Desktop::getInstance().getDisplays().getMainDisplay().dpi / standardDPI;
- juce::Desktop::getInstance().setGlobalScaleFactor(scaleFactor);
- }
-#endif
-
- setContentOwned(mainContentComponent, true);
- centreWithSize(width, height);
- setResizable(true, true);
- setResizeLimits(minWidth, minHeight, (std::numeric_limits<int>::max)(), (std::numeric_limits<int>::max)());
- }
-
- ExtensionWindow::~ExtensionWindow()
- {
- juce::LookAndFeel::setDefaultLookAndFeel(nullptr);
- }
-
- int ExtensionWindow::getDesktopWindowStyleFlags() const
- {
- return juce::ComponentPeer::windowHasCloseButton | juce::ComponentPeer::windowHasTitleBar |
- juce::ComponentPeer::windowIsResizable | juce::ComponentPeer::windowHasMinimiseButton |
- juce::ComponentPeer::windowAppearsOnTaskbar | juce::ComponentPeer::windowHasMaximiseButton;
- }
-
- void ExtensionWindow::userTriedToCloseWindow()
- {
- setVisible(false);
- }
-
- void ExtensionWindow::resized()
- {
- juce::ResizableWindow::resized();
- }
-
-} // namespace AK::ReaWwise
diff --git a/src/extension/ExtensionWindow.h b/src/extension/ExtensionWindow.h
@@ -1,23 +0,0 @@
-#pragma once
-
-#include "Core/DawContext.h"
-#include "Theme/CustomLookAndFeel.h"
-
-namespace AK::ReaWwise
-{
- class ExtensionWindow : public juce::ResizableWindow
- {
- public:
- ExtensionWindow(WwiseTransfer::DawContext& dawContext);
- ~ExtensionWindow() override;
-
- int getDesktopWindowStyleFlags() const override;
- void userTriedToCloseWindow() override;
-
- protected:
- void resized() override;
-
- private:
- WwiseTransfer::CustomLookAndFeel lookAndFeel;
- };
-} // namespace AK::ReaWwise
diff --git a/src/extension/IReaperPlugin.h b/src/extension/IReaperPlugin.h
@@ -0,0 +1,27 @@
+#pragma once
+
+class ReaProject;
+
+class IReaperPlugin
+{
+public:
+ virtual ~IReaperPlugin() = default;
+
+ virtual int getCallerVersion() const = 0;
+ virtual int registerFunction(const char* name, void* infoStruct) const = 0;
+ virtual bool isValid() const = 0;
+ virtual void* getMainHwnd() = 0;
+ virtual bool addExtensionsMainMenu() = 0;
+ virtual ReaProject* enumProjects(int idx, char* projfnOutOptional, int projfnOutOptional_sz) = 0;
+ virtual int resolveRenderPattern(ReaProject* proj, const char* path, const char* pattern, char* targets, int targets_sz) = 0;
+ virtual void main_OnCommand(int command, int flag) = 0;
+ virtual int getProjExtState(ReaProject* proj, const char* extname, const char* key, char* valOutNeedBig, int valOutNeedBig_sz) = 0;
+ virtual int setProjExtState(ReaProject* proj, const char* extname, const char* key, const char* value) = 0;
+ virtual void markProjectDirty(ReaProject* proj) = 0;
+ virtual int getProjectStateChangeCount(ReaProject* proj) = 0;
+ virtual double getSetProjectInfo(ReaProject* proj, const char* desc, double value, bool is_set) = 0;
+ virtual bool getSetProjectInfo_String(ReaProject* project, const char* desc, char* valuestrNeedBig, bool is_set) = 0;
+ virtual int reallocCmdRegisterBuf(char** ptr, int* ptr_size) = 0;
+ virtual void reallocCmdClear(int tok) = 0;
+ virtual bool supportsReallocCommands() = 0;
+};
diff --git a/src/extension/MacHelpers.h b/src/extension/MacHelpers.h
@@ -1,8 +0,0 @@
-#pragma once
-
-#include <juce_gui_basics/juce_gui_basics.h>
-
-namespace AK::ReaWwise::MacHelpers
-{
- void makeWindowFloatingPanel(juce::Component* component);
-}
diff --git a/src/extension/MacHelpers.mm b/src/extension/MacHelpers.mm
@@ -1,15 +0,0 @@
-#include "MacHelpers.h"
-
-#include <Cocoa/Cocoa.h>
-
-namespace AK::ReaWwise::MacHelpers
-{
- void makeWindowFloatingPanel(juce::Component* component)
- {
- juce::ComponentPeer* componentPeer = component->getPeer();
- componentPeer->setAlwaysOnTop(true);
- NSView* const nativeHandle = (NSView*)(componentPeer->getNativeHandle());
- NSWindow *window = [nativeHandle window];
- [window setHidesOnDeactivate:YES];
- }
-}
-\ No newline at end of file
diff --git a/src/extension/ReaperContext.cpp b/src/extension/ReaperContext.cpp
@@ -1,22 +1,30 @@
#include "ReaperContext.h"
+#include "Helpers/StringHelper.h"
#include "Helpers/WwiseHelper.h"
#include "Model/Wwise.h"
+#include <regex>
+
namespace AK::ReaWwise
{
namespace ReaperContextConstants
{
- constexpr int defaultBufferSize = 4096;
+ constexpr int defaultBufferSize = 4 * 1024;
+ constexpr int largeBufferSize = 4 * 1024 * 1024;
const juce::String stateSizeKey = "stateSize";
const juce::String stateKey = "state";
const juce::String applicationKey = "ReaWwise";
const juce::String defaultRenderPattern = "untitled";
} // namespace ReaperContextConstants
- ReaperContext::ReaperContext(ReaperPluginInterface& pluginInfo)
- : reaperPluginInterface(pluginInfo)
- , defaultRenderDirectory(juce::File::getSpecialLocation(juce::File::userDocumentsDirectory).getChildFile("REAPER Media"))
+ enum ReaperCommands
+ {
+ Render = 42230
+ };
+
+ ReaperContext::ReaperContext(IReaperPlugin& reaperPlugin)
+ : reaperPlugin(reaperPlugin)
{
}
@@ -42,10 +50,10 @@ namespace AK::ReaWwise
const auto applicationStateString = applicationState.toXmlString();
const auto applicationStateStringSize = juce::String(applicationStateString.getNumBytesAsUTF8());
- if(reaperPluginInterface.setProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateSizeKey.toUTF8(), applicationStateStringSize.toUTF8()) &&
- reaperPluginInterface.setProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateKey.toUTF8(), applicationStateString.toUTF8()))
+ if(reaperPlugin.setProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateSizeKey.toUTF8(), applicationStateStringSize.toUTF8()) &&
+ reaperPlugin.setProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateKey.toUTF8(), applicationStateString.toUTF8()))
{
- reaperPluginInterface.markProjectDirty(projectInfo.projectReference);
+ reaperPlugin.markProjectDirty(projectInfo.projectReference);
return true;
}
@@ -61,34 +69,92 @@ namespace AK::ReaWwise
auto projectInfo = getProjectInfo();
std::string buffer(defaultBufferSize, '\0');
- reaperPluginInterface.getProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateSizeKey.toUTF8(), &buffer[0], buffer.size());
+ reaperPlugin.getProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateSizeKey.toUTF8(), &buffer[0], buffer.size());
const auto stateSize = std::strtoll(&buffer[0], nullptr, 10);
if(stateSize == 0)
return {};
buffer.resize(stateSize);
- if(reaperPluginInterface.getProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateKey.toUTF8(), &buffer[0], buffer.size()))
+ if(reaperPlugin.getProjExtState(projectInfo.projectReference, applicationKey.toUTF8(), stateKey.toUTF8(), &buffer[0], buffer.size()))
return juce::ValueTree::fromXml(buffer);
return {};
}
- std::vector<juce::String> ReaperContext::splitDoubleNullTerminatedString(const char* buffer)
+ void ReaperContext::renderItems()
+ {
+ reaperPlugin.main_OnCommand(ReaperCommands::Render, 0);
+ }
+
+ std::vector<juce::String> ReaperContext::getRenderTargets()
{
- std::vector<juce::String> result;
+ auto projectInfo = getProjectInfo();
+
+ std::vector<juce::String> renderTargets;
+
+ auto result = getProjectStringBuffer(projectInfo.projectReference, "RENDER_TARGETS_EX");
- for(const char* current = buffer; current && *current; current += result.back().length() + 1)
+ if(result.status)
+ renderTargets = WwiseTransfer::StringHelper::splitDoubleNullTerminatedString(result.buffer);
+ else
{
- result.emplace_back(current);
+ // For REAPER < 6.69
+ auto renderTargetsString = getProjectString(projectInfo.projectReference, "RENDER_TARGETS");
+
+ juce::StringArray renderTargetsStringArray;
+ renderTargetsStringArray.addTokens(renderTargetsString, ";", "");
+ renderTargetsStringArray.removeEmptyStrings();
+
+ renderTargets = std::vector<juce::String>(renderTargetsStringArray.strings.begin(), renderTargetsStringArray.strings.end());
}
- return result;
+ return renderTargets;
}
- void ReaperContext::renderItems()
+ juce::String ReaperContext::getProjectString(ReaProject* proj, const char* key) const
{
- reaperPluginInterface.main_OnCommand(42230, 0);
+ auto result = getProjectStringBuffer(proj, key);
+
+ if(result.buffer.size() > 0)
+ return juce::String(&result.buffer[0], result.buffer.size());
+
+ return {};
+ }
+
+ ReaperContext::ProjectStringBufferResult ReaperContext::getProjectStringBuffer(ReaProject* proj, const char* key) const
+ {
+ ProjectStringBufferResult result;
+
+ if(reaperPlugin.supportsReallocCommands())
+ {
+ // For REAPER 6.68+
+ char buffer[ReaperContextConstants::defaultBufferSize];
+ char* bufferPtr = buffer;
+
+ int bufferSize = (int)sizeof(buffer);
+
+ int token = reaperPlugin.reallocCmdRegisterBuf(&bufferPtr, &bufferSize);
+
+ result.status = reaperPlugin.getSetProjectInfo_String(proj, key, bufferPtr, false);
+
+ if(result.status)
+ result.buffer.assign(bufferPtr, bufferPtr + bufferSize);
+
+ reaperPlugin.reallocCmdClear(token);
+ }
+ else
+ {
+ static std::vector<char> buffer(ReaperContextConstants::largeBufferSize);
+ std::fill(buffer.begin(), buffer.end(), '\0');
+
+ result.status = reaperPlugin.getSetProjectInfo_String(proj, key, &buffer[0], false);
+
+ if(result.status)
+ result.buffer = buffer;
+ }
+
+ return result;
}
std::vector<WwiseTransfer::Import::Item> ReaperContext::getItemsForImport(const WwiseTransfer::Import::Options& options)
@@ -102,57 +168,107 @@ namespace AK::ReaWwise
return importItems;
auto projectInfo = getProjectInfo();
- const auto renderDirectory = getRenderDirectory(projectInfo);
- juce::StringArray temp;
- juce::String renderStats = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_STATS");
- temp.addTokens(renderStats, ";", "");
+ juce::String renderStats = getProjectString(projectInfo.projectReference, "RENDER_STATS");
- juce::StringArray finalRenderPaths;
+ static juce::String fileToken("FILE:");
+ static juce::String delimiter(';' + fileToken);
- // Not all items in here are paths. Paths are prefixed with "FILE:"
- for(auto item : temp)
+ if(renderStats.isNotEmpty())
{
- if(item.startsWith("FILE:"))
- finalRenderPaths.add(item.trimCharactersAtStart("FILE:"));
- }
+ // To ease parsing, append ";FILE:" to the end of renderStats
+ if(renderStats.endsWithChar(';'))
+ renderStats << fileToken;
+ else
+ renderStats << delimiter;
- for(int i = 0; i < importItemsForPreview.size(); ++i)
- {
- importItems.push_back({importItemsForPreview[i].path, importItemsForPreview[i].originalsSubFolder, importItemsForPreview[i].audioFilePath, finalRenderPaths[i]});
+ int endPosition, startPosition = renderStats.indexOf(fileToken) + fileToken.length();
+
+ if(startPosition != -1) // If we don't find the first "FILE:", exit since we are receiving something unexpected
+ {
+ static std::regex regex("(.+?);[A-Z]+");
+
+ while((endPosition = renderStats.indexOf(startPosition, delimiter)) != -1)
+ {
+ auto finalRenderPath = renderStats.substring(startPosition, endPosition).toStdString();
+
+ std::smatch results;
+ if(std::regex_search(finalRenderPath, results, regex))
+ finalRenderPath = results[1];
+
+ const auto& importItemForPreview = importItemsForPreview[importItems.size()];
+
+ importItems.push_back({
+ importItemForPreview.path,
+ importItemForPreview.originalsSubFolder,
+ importItemForPreview.audioFilePath,
+ finalRenderPath,
+ });
+
+ startPosition = endPosition + delimiter.length();
+ }
+ }
}
return importItems;
}
- std::vector<WwiseTransfer::Import::PreviewItem> ReaperContext::getItemsForPreview(const WwiseTransfer::Import::Options& options)
+ std::vector<juce::String> ReaperContext::getOriginalSubfolders(const ProjectInfo& projectInfo, const juce::String& originalsSubfolder)
{
- juce::ScopedLock lock{apiAccess};
+ // The originals subfolder is a combination of what the user inputs in the originals subfolder input field
+ // Combined with anything in the render file path after the render folder
- auto projectInfo = getProjectInfo();
+ // To get the resolved file paths relative to the render directory, we can simply subtract the parent paths in resolvedDummyRenderPattern from
+ // the file paths in resolvedRenderPattern. Other approaches require us to know the render directory which is difficult to figure out and
+ // requires alot of logic on our end.
- const auto renderDirectory = getRenderDirectory(projectInfo);
- const auto originalsSubfolderPathPart = options.originalsSubfolder + juce::File::getSeparatorString();
+ const auto dummyRenderPattern = juce::File::getSeparatorString();
+ const auto resolvedDummyRenderPattern = getItemListFromRenderPattern(projectInfo.projectReference, dummyRenderPattern, true);
- const auto resolvedOriginalsSubfolder = getItemListFromRenderPattern(projectInfo.projectReference, originalsSubfolderPathPart, false);
+ const auto renderPattern = getRenderPattern(projectInfo);
+ const auto resolvedRenderPattern = getItemListFromRenderPattern(projectInfo.projectReference, renderPattern, true);
- juce::StringArray renderTargets;
- juce::String renderTargetsString = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_TARGETS");
- renderTargets.addTokens(renderTargetsString, ";", "");
- renderTargets.removeEmptyStrings();
+ const auto originalsSubfolderRenderPattern = originalsSubfolder + juce::File::getSeparatorString();
+ const auto resolvedOriginalsSubfolder = getItemListFromRenderPattern(projectInfo.projectReference, originalsSubfolderRenderPattern, true);
- if(renderTargets.size() != resolvedOriginalsSubfolder.size())
+ if(resolvedDummyRenderPattern.size() != resolvedRenderPattern.size() && resolvedDummyRenderPattern.size() != resolvedOriginalsSubfolder.size())
{
- juce::Logger::writeToLog("Reaper: Mismatch between renderTargets and resolvedOriginalsSubfolder");
+ juce::Logger::writeToLog("Reaper: Mismatch between resolvedDummyRenderPattern, resolvedRenderPattern and resolvedOriginalsSubfolder");
return {};
}
+ std::vector<juce::String> finalOriginalsSubfolders;
+ for(int i = 0; i < resolvedDummyRenderPattern.size(); ++i)
+ {
+ auto renderDirectory = juce::File(resolvedDummyRenderPattern[i]).getParentDirectory();
+ auto relativeResolvedRenderPattern = juce::File(resolvedRenderPattern[i]).getRelativePathFrom(renderDirectory);
+ auto originalsSubfolderFile = juce::File(resolvedOriginalsSubfolder[i]).getParentDirectory().getChildFile(relativeResolvedRenderPattern);
+
+ juce::String originalsSubfolder = "";
+ if(originalsSubfolderFile.getParentDirectory() != renderDirectory)
+ originalsSubfolder = originalsSubfolderFile.getRelativePathFrom(renderDirectory).upToLastOccurrenceOf(juce::File::getSeparatorString(), false, true);
+
+ finalOriginalsSubfolders.push_back(originalsSubfolder);
+ }
+
+ return finalOriginalsSubfolders;
+ }
+
+ std::vector<WwiseTransfer::Import::PreviewItem> ReaperContext::getItemsForPreview(const WwiseTransfer::Import::Options& options)
+ {
+ juce::ScopedLock lock{apiAccess};
+
+ auto projectInfo = getProjectInfo();
+
+ auto renderTargets = getRenderTargets();
+ auto resolvedOriginalsSubfolder = getOriginalSubfolders(projectInfo, options.originalsSubfolder);
+
const auto objectPathsPattern = options.importDestination + options.hierarchyMappingPath;
std::vector<juce::String> resolvedObjectPaths = getItemListFromRenderPattern(projectInfo.projectReference, objectPathsPattern, false);
- if(resolvedObjectPaths.size() != renderTargets.size() || resolvedObjectPaths.size() != resolvedOriginalsSubfolder.size())
+ if(renderTargets.size() != resolvedOriginalsSubfolder.size() || renderTargets.size() != resolvedObjectPaths.size())
{
- juce::Logger::writeToLog("Reaper: Mismatch between resolvedObjectPaths, renderTargets and resolvedOriginalsSubfolder");
+ juce::Logger::writeToLog("Reaper: Mismatch between renderTargets, resolvedObjectPaths and resolvedOriginalsSubfolder");
return {};
}
@@ -160,27 +276,8 @@ namespace AK::ReaWwise
for(int i = 0; i < resolvedObjectPaths.size(); ++i)
{
const auto& objectPath = resolvedObjectPaths[i].upToLastOccurrenceOf(".", false, false);
- const auto& renderTarget = renderTargets[i];
-
- // Get the parent of the render target, as a relative path against the render directory
- // We want to preserve this hierarchy in the wwise originals folder
- juce::String relativeParentDir;
- if(juce::File(renderTarget).getParentDirectory() != renderDirectory)
- relativeParentDir = juce::File(renderTarget).getRelativePathFrom(renderDirectory).upToLastOccurrenceOf(juce::File::getSeparatorString(), false, true);
-
- // Reaper may append a differentiator at the end of a path during pattern resolving. We don't care about it.
- auto originalsSubfolder = resolvedOriginalsSubfolder[i].upToLastOccurrenceOf(juce::File::getSeparatorString(), false, true);
-
- // The originalsSubfolder is a combination of what the user inputs in the gui (resolvedOriginalsSubfolder) and the directory structure under the render directory.
- if(relativeParentDir.isNotEmpty())
- {
- if(originalsSubfolder.isNotEmpty())
- originalsSubfolder << juce::File::getSeparatorString();
- originalsSubfolder << relativeParentDir;
- }
-
- importItems.push_back({objectPath, originalsSubfolder, renderTarget});
+ importItems.push_back({objectPath, resolvedOriginalsSubfolder[i], renderTargets[i]});
}
return importItems;
@@ -191,7 +288,7 @@ namespace AK::ReaWwise
std::string buffer(ReaperContextConstants::defaultBufferSize, '\0');
// The buffer sent to enumProjects will contain the project path.
- auto projectReference = reaperPluginInterface.enumProjects(-1, &buffer[0], buffer.size());
+ auto projectReference = reaperPlugin.enumProjects(-1, &buffer[0], buffer.size());
if(!projectReference || buffer.empty())
return {};
@@ -212,7 +309,7 @@ namespace AK::ReaWwise
// There are several scenarios where the render pattern could be empty
// 1. When the project hasn't been saved (reaper uses "untitled")
// 2. When the project has been saved (reaper uses the project name)
- auto renderPattern = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_PATTERN");
+ auto renderPattern = getProjectString(projectInfo.projectReference, "RENDER_PATTERN");
if(renderPattern.isNotEmpty())
return renderPattern;
@@ -222,30 +319,17 @@ namespace AK::ReaWwise
return projectInfo.projectName;
}
- juce::File ReaperContext::getRenderDirectory(const ReaperContext::ProjectInfo& projectInfo) const
- {
- auto renderDirectoryPath = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_FILE");
- if(juce::File::isAbsolutePath(renderDirectoryPath))
- return juce::File(renderDirectoryPath);
-
- if(projectInfo.projectPath.isNotEmpty())
- return juce::File(projectInfo.projectPath).getParentDirectory().getChildFile(renderDirectoryPath);
-
- // If the project wasnt saved, reaper uses a general render directory
- return defaultRenderDirectory.getChildFile(renderDirectoryPath);
- }
-
bool ReaperContext::sessionChanged()
{
auto sessionChanged = false;
auto projectInfo = getProjectInfo();
- auto projectStateCount = reaperPluginInterface.getProjectStateChangeCount(projectInfo.projectReference);
- auto renderSource = reaperPluginInterface.getSetProjectInfo(projectInfo.projectReference, "RENDER_SETTINGS", 0, false);
- auto renderBounds = reaperPluginInterface.getSetProjectInfo(projectInfo.projectReference, "RENDER_BOUNDSFLAG", 0, false);
- auto renderFile = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_FILE");
- auto renderPattern = reaperPluginInterface.getProjectString(projectInfo.projectReference, "RENDER_PATTERN");
+ auto projectStateCount = reaperPlugin.getProjectStateChangeCount(projectInfo.projectReference);
+ auto renderSource = reaperPlugin.getSetProjectInfo(projectInfo.projectReference, "RENDER_SETTINGS", 0, false);
+ auto renderBounds = reaperPlugin.getSetProjectInfo(projectInfo.projectReference, "RENDER_BOUNDSFLAG", 0, false);
+ auto renderFile = getProjectString(projectInfo.projectReference, "RENDER_FILE");
+ auto renderPattern = getProjectString(projectInfo.projectReference, "RENDER_PATTERN");
if(projectStateCount != stateInfo.projectStateCount ||
renderSource != stateInfo.renderSource ||
@@ -268,10 +352,13 @@ namespace AK::ReaWwise
std::vector<juce::String> ReaperContext::getItemListFromRenderPattern(ReaProject* project, const juce::String& pattern, bool suppressIllegalPaths)
{
- const int bufferLength = reaperPluginInterface.resolveRenderPattern(project, suppressIllegalPaths ? "" : nullptr, pattern.toUTF8(), nullptr, 0);
- std::string buffer(bufferLength, '\0');
+ const int bufferLength = reaperPlugin.resolveRenderPattern(project, suppressIllegalPaths ? "" : nullptr, pattern.toUTF8(), nullptr, 0);
+
+ if(bufferLength == 0)
+ return {};
- const int newBufferLength = reaperPluginInterface.resolveRenderPattern(project, suppressIllegalPaths ? "" : nullptr, pattern.toUTF8(), &buffer[0], bufferLength);
+ std::vector<char> buffer(bufferLength, '\0');
+ const int newBufferLength = reaperPlugin.resolveRenderPattern(project, suppressIllegalPaths ? "" : nullptr, pattern.toUTF8(), &buffer[0], bufferLength);
if(newBufferLength > bufferLength)
{
// It is possible the resolved render pattern changes between the two calls to resolveRenderPattern.
@@ -281,6 +368,6 @@ namespace AK::ReaWwise
return {};
}
- return splitDoubleNullTerminatedString(&buffer[0]);
+ return WwiseTransfer::StringHelper::splitDoubleNullTerminatedString(buffer);
}
} // namespace AK::ReaWwise
diff --git a/src/extension/ReaperContext.h b/src/extension/ReaperContext.h
@@ -1,38 +1,16 @@
#pragma once
#include "Core/DawContext.h"
+#include "IReaperPlugin.h"
#include "Model/Import.h"
-class ReaProject;
namespace AK::ReaWwise
{
- class ReaperPluginInterface
- {
- public:
- virtual ~ReaperPluginInterface() = default;
-
- virtual int getCallerVersion() const = 0;
- virtual int registerFunction(const char* name, void* infoStruct) const = 0;
- virtual bool isValid() const = 0;
-
- virtual void* getMainHwnd() = 0;
- virtual bool addExtensionsMainMenu() = 0;
- virtual ReaProject* enumProjects(int idx, char* projfnOutOptional, int projfnOutOptional_sz) = 0;
- virtual juce::String getProjectString(ReaProject* project, const char* key) = 0;
- virtual int resolveRenderPattern(ReaProject* proj, const char* path, const char* pattern, char* targets, int targets_sz) = 0;
- virtual void main_OnCommand(int command, int flag) = 0;
- virtual int getProjExtState(ReaProject* proj, const char* extname, const char* key, char* valOutNeedBig, int valOutNeedBig_sz) = 0;
- virtual int setProjExtState(ReaProject* proj, const char* extname, const char* key, const char* value) = 0;
- virtual void markProjectDirty(ReaProject* proj) = 0;
- virtual int getProjectStateChangeCount(ReaProject* proj) = 0;
- virtual double getSetProjectInfo(ReaProject* proj, const char* desc, double value, bool is_set) = 0;
- };
-
class ReaperContext
: public WwiseTransfer::DawContext
{
public:
- ReaperContext(ReaperPluginInterface& pluginInfo);
+ ReaperContext(IReaperPlugin& pluginInfo);
~ReaperContext() override;
bool sessionChanged() override;
@@ -60,18 +38,22 @@ namespace AK::ReaWwise
juce::String renderPattern;
};
- std::vector<juce::String> splitDoubleNullTerminatedString(const char*);
+ struct ProjectStringBufferResult
+ {
+ bool status{false};
+ std::vector<char> buffer;
+ };
+
std::vector<juce::String> getItemListFromRenderPattern(ReaProject* project, const juce::String& pattern, bool suppressIllegalPaths = true);
ProjectInfo getProjectInfo() const;
juce::String getRenderPattern(const ProjectInfo& projectInfo) const;
- juce::File getRenderDirectory(const ProjectInfo& projectInfo) const;
-
- juce::File defaultRenderDirectory;
+ std::vector<juce::String> getOriginalSubfolders(const ProjectInfo& projectInfo, const juce::String& originalsSubfolder);
+ std::vector<juce::String> getRenderTargets();
+ juce::String getProjectString(ReaProject* proj, const char* key) const;
+ ProjectStringBufferResult getProjectStringBuffer(ReaProject* proj, const char* key) const;
juce::CriticalSection apiAccess;
-
- ReaperPluginInterface& reaperPluginInterface;
-
+ IReaperPlugin& reaperPlugin;
StateInfo stateInfo;
};
} // namespace AK::ReaWwise
diff --git a/src/extension/ReaperPlugin.cpp b/src/extension/ReaperPlugin.cpp
@@ -0,0 +1,121 @@
+#include "ReaperPlugin.h"
+
+namespace AK::ReaWwise
+{
+
+ ReaperPlugin::ReaperPlugin(reaper_plugin_info_t* pluginInfo)
+ : pluginInfo(pluginInfo)
+ {
+ _getMainHwnd = decltype(GetMainHwnd)(pluginInfo->GetFunc("GetMainHwnd"));
+ _addExtensionsMainMenu = decltype(AddExtensionsMainMenu)(pluginInfo->GetFunc("AddExtensionsMainMenu"));
+ _enumProjects = decltype(EnumProjects)(pluginInfo->GetFunc("EnumProjects"));
+ _getSetProjectInfo_String = decltype(GetSetProjectInfo_String)(pluginInfo->GetFunc("GetSetProjectInfo_String"));
+ _resolveRenderPattern = decltype(ResolveRenderPattern)(pluginInfo->GetFunc("ResolveRenderPattern"));
+ _main_OnCommand = decltype(Main_OnCommand)(pluginInfo->GetFunc("Main_OnCommand"));
+ _getProjExtState = decltype(GetProjExtState)(pluginInfo->GetFunc("GetProjExtState"));
+ _setProjExtState = decltype(SetProjExtState)(pluginInfo->GetFunc("SetProjExtState"));
+ _markProjectDirty = decltype(MarkProjectDirty)(pluginInfo->GetFunc("MarkProjectDirty"));
+ _getProjectStateChangeCount = decltype(GetProjectStateChangeCount)(pluginInfo->GetFunc("GetProjectStateChangeCount"));
+ _getSetProjectInfo = decltype(GetSetProjectInfo)(pluginInfo->GetFunc("GetSetProjectInfo"));
+ _realloc_cmd_register_buf = decltype(realloc_cmd_register_buf)(pluginInfo->GetFunc("realloc_cmd_register_buf"));
+ _realloc_cmd_clear = decltype(realloc_cmd_clear)(pluginInfo->GetFunc("realloc_cmd_clear"));
+ }
+
+ int ReaperPlugin::getCallerVersion() const
+ {
+ return pluginInfo->caller_version;
+ }
+
+ int ReaperPlugin::registerFunction(const char* name, void* infoStruct) const
+ {
+ return pluginInfo->Register(name, infoStruct);
+ }
+
+ bool ReaperPlugin::isValid() const
+ {
+ if(_getMainHwnd &&
+ _addExtensionsMainMenu &&
+ _enumProjects &&
+ _getSetProjectInfo_String &&
+ _resolveRenderPattern &&
+ _main_OnCommand &&
+ _getProjExtState &&
+ _setProjExtState &&
+ _markProjectDirty &&
+ _getProjectStateChangeCount &&
+ _getSetProjectInfo)
+ return true;
+
+ return false;
+ }
+
+ void* ReaperPlugin::getMainHwnd()
+ {
+ return _getMainHwnd();
+ }
+
+ bool ReaperPlugin::addExtensionsMainMenu()
+ {
+ return _addExtensionsMainMenu();
+ }
+
+ ReaProject* ReaperPlugin::enumProjects(int idx, char* projfnOutOptional, int projfnOutOptional_sz)
+ {
+ return _enumProjects(idx, projfnOutOptional, projfnOutOptional_sz);
+ }
+
+ int ReaperPlugin::resolveRenderPattern(ReaProject* project, const char* path, const char* pattern, char* targets, int targets_sz)
+ {
+ return _resolveRenderPattern(project, path, pattern, targets, targets_sz);
+ }
+
+ void ReaperPlugin::main_OnCommand(int command, int flag)
+ {
+ _main_OnCommand(command, flag);
+ }
+
+ int ReaperPlugin::getProjExtState(ReaProject* proj, const char* extname, const char* key, char* valOutNeedBig, int valOutNeedBig_sz)
+ {
+ return _getProjExtState(proj, extname, key, valOutNeedBig, valOutNeedBig_sz);
+ }
+
+ int ReaperPlugin::setProjExtState(ReaProject* proj, const char* extname, const char* key, const char* value)
+ {
+ return _setProjExtState(proj, extname, key, value);
+ }
+
+ void ReaperPlugin::markProjectDirty(ReaProject* proj)
+ {
+ return _markProjectDirty(proj);
+ }
+
+ int ReaperPlugin::getProjectStateChangeCount(ReaProject* proj)
+ {
+ return _getProjectStateChangeCount(proj);
+ }
+
+ double ReaperPlugin::getSetProjectInfo(ReaProject* proj, const char* desc, double value, bool is_set)
+ {
+ return _getSetProjectInfo(proj, desc, value, is_set);
+ }
+
+ bool ReaperPlugin::getSetProjectInfo_String(ReaProject* project, const char* desc, char* valuestrNeedBig, bool is_set)
+ {
+ return _getSetProjectInfo_String(project, desc, valuestrNeedBig, is_set);
+ }
+
+ int ReaperPlugin::reallocCmdRegisterBuf(char** ptr, int* ptr_size)
+ {
+ return _realloc_cmd_register_buf(ptr, ptr_size);
+ }
+
+ void ReaperPlugin::reallocCmdClear(int tok)
+ {
+ _realloc_cmd_clear(tok);
+ }
+
+ bool ReaperPlugin::supportsReallocCommands()
+ {
+ return _realloc_cmd_register_buf && _realloc_cmd_clear;
+ }
+} // namespace AK::ReaWwise
diff --git a/src/extension/ReaperPlugin.h b/src/extension/ReaperPlugin.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include "IReaperPlugin.h"
+
+#include <reaper_plugin_functions.h>
+
+namespace AK::ReaWwise
+{
+ class ReaperPlugin : public IReaperPlugin
+ {
+ public:
+ ReaperPlugin(reaper_plugin_info_t* pluginInfo);
+ ~ReaperPlugin() override = default;
+
+ int getCallerVersion() const override;
+ int registerFunction(const char* name, void* infoStruct) const override;
+ bool isValid() const override;
+ void* getMainHwnd() override;
+ bool addExtensionsMainMenu() override;
+ ReaProject* enumProjects(int idx, char* projfnOutOptional, int projfnOutOptional_sz) override;
+ int resolveRenderPattern(ReaProject* project, const char* path, const char* pattern, char* targets, int targets_sz) override;
+ void main_OnCommand(int command, int flag) override;
+ int getProjExtState(ReaProject* proj, const char* extname, const char* key, char* valOutNeedBig, int valOutNeedBig_sz) override;
+ int setProjExtState(ReaProject* proj, const char* extname, const char* key, const char* value) override;
+ void markProjectDirty(ReaProject* proj) override;
+ int getProjectStateChangeCount(ReaProject* proj) override;
+ double getSetProjectInfo(ReaProject* proj, const char* desc, double value, bool is_set) override;
+ bool getSetProjectInfo_String(ReaProject* project, const char* desc, char* valuestrNeedBig, bool is_set) override;
+ int reallocCmdRegisterBuf(char** ptr, int* ptr_size) override;
+ void reallocCmdClear(int tok) override;
+ bool supportsReallocCommands() override;
+
+ private:
+ reaper_plugin_info_t* pluginInfo;
+ decltype(GetMainHwnd) _getMainHwnd;
+ decltype(AddExtensionsMainMenu) _addExtensionsMainMenu;
+ decltype(EnumProjects) _enumProjects;
+ decltype(GetSetProjectInfo_String) _getSetProjectInfo_String;
+ decltype(ResolveRenderPattern) _resolveRenderPattern;
+ decltype(Main_OnCommand) _main_OnCommand;
+ decltype(GetProjExtState) _getProjExtState;
+ decltype(SetProjExtState) _setProjExtState;
+ decltype(MarkProjectDirty) _markProjectDirty;
+ decltype(GetProjectStateChangeCount) _getProjectStateChangeCount;
+ decltype(GetSetProjectInfo) _getSetProjectInfo;
+ decltype(realloc_cmd_register_buf) _realloc_cmd_register_buf;
+ decltype(realloc_cmd_clear) _realloc_cmd_clear;
+ };
+} // namespace AK::ReaWwise
diff --git a/src/shared/Core/DawWatcher.cpp b/src/shared/Core/DawWatcher.cpp
@@ -32,6 +32,7 @@ namespace AK::WwiseTransfer
, projectPath(appState, IDs::projectPath, nullptr)
, originalsFolder(appState, IDs::originalsFolder, nullptr)
, languageSubfolder(appState, IDs::languageSubfolder, nullptr)
+ , waapiConnected(appState, IDs::waapiConnected, nullptr)
, dawContext(dawContext)
, waapiClient(waapiClient)
, lastImportItemsHash(0)
@@ -176,7 +177,7 @@ namespace AK::WwiseTransfer
previewLoading = false;
};
- if(importDestination.get().isNotEmpty())
+ if(waapiConnected.get() && importDestination.get().isNotEmpty())
{
previewLoading = true;
@@ -196,7 +197,7 @@ namespace AK::WwiseTransfer
void DawWatcher::valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property)
{
static std::initializer_list<juce::Identifier> properties{IDs::containerNameExists, IDs::projectPath, IDs::originalsFolder,
- IDs::wwiseObjectsChanged, IDs::waqlEnabled, IDs::languageSubfolder, IDs::originalsFolder, IDs::importDestination, IDs::originalsSubfolder};
+ IDs::wwiseObjectsChanged, IDs::waqlEnabled, IDs::languageSubfolder, IDs::originalsFolder, IDs::importDestination, IDs::originalsSubfolder, IDs::waapiConnected};
if(treeWhosePropertyHasChanged == applicationState && std::find(properties.begin(), properties.end(), property) != properties.end() ||
treeWhosePropertyHasChanged.getType() == IDs::hierarchyMappingNode)
diff --git a/src/shared/Core/DawWatcher.h b/src/shared/Core/DawWatcher.h
@@ -33,6 +33,7 @@ namespace AK::WwiseTransfer
juce::CachedValue<juce::String> projectPath;
juce::CachedValue<juce::String> originalsFolder;
juce::CachedValue<juce::String> languageSubfolder;
+ juce::CachedValue<bool> waapiConnected;
DawContext& dawContext;
WaapiClient& waapiClient;
diff --git a/src/shared/Core/WaapiClient.cpp b/src/shared/Core/WaapiClient.cpp
@@ -28,6 +28,11 @@ namespace AK::WwiseTransfer
static constexpr const char* const commandsExecute = "ak.wwise.ui.commands.execute";
} // namespace WaapiCommands
+ namespace WaapiURIs
+ {
+ static constexpr const char* const unknownObject = "ak.wwise.query.unknown_object";
+ }
+
WaapiClientWatcher::WaapiClientWatcher(juce::ValueTree appState, WaapiClient& waapiClient, WaapiClientWatcherConfig waapiClientWatcherConfig)
: juce::Thread("WaapiService")
, applicationState(appState)
@@ -695,6 +700,68 @@ namespace AK::WwiseTransfer
return response;
}
+ Waapi::Response<Waapi::ObjectResponse> WaapiClient::getObject(const juce::String& objectPath)
+ {
+ using namespace WwiseAuthoringAPI;
+
+ Waapi::Response<Waapi::ObjectResponse> response;
+
+ const auto args = AkJson::Map{
+ {
+ "from",
+ AkJson::Map{
+ {
+ "path",
+ AkJson::Array{
+ AkVariant{objectPath.toStdString()},
+ },
+ },
+ },
+ },
+ };
+
+ static const auto options = AkJson::Map{
+ {
+ "return",
+ AkJson::Array{
+ AkVariant{"id"},
+ AkVariant{"name"},
+ AkVariant{"type"},
+ AkVariant{"path"},
+ AkVariant{"sound:originalWavFilePath"},
+ AkVariant{"workunitType"},
+ },
+ },
+ };
+
+ AkJson result;
+ response.status = call(WaapiCommands::objectGet, args, options, result);
+
+ if(response.status)
+ {
+ if(result.HasKey("return"))
+ {
+ auto objects = result["return"].GetArray();
+
+ for(auto& object : objects)
+ {
+ response.result = object;
+ }
+ }
+ }
+ // Special Case: The call actually succeeds but does not find the object
+ else if(result.HasKey("uri") && result["uri"].GetVariant().GetString() == WaapiURIs::unknownObject)
+ {
+ response.status = true;
+ }
+ else
+ {
+ response.errorMessage << WaapiHelper::getErrorMessage(result);
+ }
+
+ return response;
+ }
+
void WaapiClient::beginUndoGroup()
{
using namespace WwiseAuthoringAPI;
diff --git a/src/shared/Core/WaapiClient.h b/src/shared/Core/WaapiClient.h
@@ -84,6 +84,7 @@ namespace AK::WwiseTransfer
Waapi::Response<Waapi::ObjectResponseSet> getObjectAncestorsAndDescendantsLegacy(const juce::String& objectPath);
Waapi::Response<std::vector<juce::String>> getProjectLanguages();
Waapi::Response<juce::String> getOriginalsFolder();
+ Waapi::Response<Waapi::ObjectResponse> getObject(const juce::String& objectPath);
bool selectObjects(const juce::String& selectObjectsCommand, const std::vector<juce::String>& objectPaths);
@@ -189,6 +190,17 @@ namespace AK::WwiseTransfer
threadPool.addJob(new AsyncJob(onJobExecute, callback), true);
}
+ template <typename Callback>
+ void getObjectAsync(const juce::String& objectPath, Callback& callback)
+ {
+ auto onJobExecute = [objectPath, this]()
+ {
+ return getObject(objectPath);
+ };
+
+ threadPool.addJob(new AsyncJob(onJobExecute, callback), true);
+ }
+
private:
juce::ThreadPool threadPool;
};
diff --git a/src/shared/Helpers/StringHelper.h b/src/shared/Helpers/StringHelper.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace AK::WwiseTransfer::StringHelper
+{
+ inline std::vector<juce::String> splitDoubleNullTerminatedString(const std::vector<char>& buffer)
+ {
+ std::vector<juce::String> stringArray;
+
+ std::vector<char> tempBuffer;
+ for(const char character : buffer)
+ {
+ if(character != '\0')
+ tempBuffer.push_back(character);
+ else if(!tempBuffer.empty())
+ {
+ stringArray.push_back(juce::String(&tempBuffer[0], tempBuffer.size()));
+ tempBuffer.clear();
+ }
+ else
+ break;
+ }
+
+ return stringArray;
+ }
+
+ inline std::vector<char> createDoubleNullTerminatedStringBuffer(const std::vector<juce::String>& strings)
+ {
+ std::vector<char> rv;
+
+ if(constexpr bool minimizeAllocations = true)
+ {
+ std::size_t size = strings.empty() ? 2 : 1;
+ std::for_each(strings.cbegin(), strings.cend(), [&size](const juce::String& string)
+ {
+ auto s = string.length();
+ size += s + (s ? 1 : 0);
+ });
+ rv.reserve(size);
+ }
+
+ std::for_each(strings.cbegin(), strings.cend(), [&rv](const juce::String& s)
+ {
+ if(s.isEmpty())
+ return;
+
+ for(const auto& c : s)
+ rv.push_back(c);
+
+ rv.push_back('\0');
+ });
+
+ if(strings.empty())
+ rv.push_back('\0');
+
+ rv.push_back('\0');
+ return rv;
+ }
+} // namespace AK::WwiseTransfer::StringHelper
diff --git a/src/shared/Helpers/WwiseHelper.h b/src/shared/Helpers/WwiseHelper.h
@@ -70,7 +70,7 @@ namespace AK::WwiseTransfer::WwiseHelper
switch(objectType)
{
case ObjectType::ActorMixer:
- return "Actor Mixer";
+ return "Actor-Mixer";
case ObjectType::AudioFileSource:
return "Audio File Source";
case ObjectType::BlendContainer:
@@ -104,7 +104,7 @@ namespace AK::WwiseTransfer::WwiseHelper
if(objectTypeAsString == "AudioFileSource" || objectTypeAsString == "Audio File Source")
return ObjectType::AudioFileSource;
- else if(objectTypeAsString == "ActorMixer" || objectTypeAsString == "Actor Mixer")
+ else if(objectTypeAsString == "ActorMixer" || objectTypeAsString == "Actor Mixer" || objectTypeAsString == "Actor-Mixer")
return ObjectType::ActorMixer;
else if(objectTypeAsString == "BlendContainer" || objectTypeAsString == "Blend Container")
return ObjectType::BlendContainer;
diff --git a/src/shared/Persistance/ApplicationStateValidator.cpp b/src/shared/Persistance/ApplicationStateValidator.cpp
@@ -6,8 +6,9 @@
namespace AK::WwiseTransfer::ApplicationState
{
- Validator::Validator(juce::ValueTree appState)
+ Validator::Validator(juce::ValueTree appState, WaapiClient& waapiClient)
: applicationState(appState)
+ , waapiClient(waapiClient)
{
applicationState.addListener(this);
}
@@ -33,16 +34,33 @@ namespace AK::WwiseTransfer::ApplicationState
valueTree.setPropertyExcludingListener(this, IDs::originalsSubfolderValid, isValid, nullptr);
valueTree.setPropertyExcludingListener(this, IDs::originalsSubfolderErrorMessage, errorMessage, nullptr);
}
- else if(property == IDs::importDestination || property == IDs::importDestinationType)
+ else if(property == IDs::importDestination)
{
const juce::String importDestination = valueTree[IDs::importDestination];
- const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(valueTree[IDs::importDestinationType]);
- auto isValid = validateImportDestination(importDestination, importDestinationType);
+ auto isValid = validateImportDestination(importDestination);
juce::String errorMessage = isValid ? "" : "Invalid import destination";
valueTree.setPropertyExcludingListener(this, IDs::importDestinationValid, isValid, nullptr);
valueTree.setPropertyExcludingListener(this, IDs::importDestinationErrorMessage, errorMessage, nullptr);
+
+ auto onGetObjectAsync = [this](const Waapi::Response<Waapi::ObjectResponse> response)
+ {
+ auto objectType = Wwise::ObjectType::VirtualFolder;
+
+ if(response.result.path.isNotEmpty())
+ objectType = response.result.type;
+
+ applicationState.setProperty(IDs::importDestinationType, juce::VariantConverter<Wwise::ObjectType>::toVar(objectType), nullptr);
+ };
+
+ waapiClient.getObjectAsync(importDestination, onGetObjectAsync);
+ }
+ else if(property == IDs::importDestinationType)
+ {
+ const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(valueTree[IDs::importDestinationType]);
+
+ validateHierarchyMapping(importDestinationType, applicationState.getChildWithName(IDs::hierarchyMapping));
}
}
else if(valueTree.getType() == IDs::hierarchyMappingNode)
@@ -50,7 +68,9 @@ namespace AK::WwiseTransfer::ApplicationState
if(property == IDs::objectType)
{
auto hierarchyMapping = valueTree.getParent();
- validateHierarchyMapping(hierarchyMapping);
+
+ const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]);
+ validateHierarchyMapping(importDestinationType, hierarchyMapping);
}
else if(property == IDs::propertyTemplatePath || property == IDs::propertyTemplatePathType)
{
@@ -67,7 +87,9 @@ namespace AK::WwiseTransfer::ApplicationState
{
if(parent.getType() == IDs::hierarchyMapping)
{
- validateHierarchyMapping(parent);
+ const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]);
+
+ validateHierarchyMapping(importDestinationType, parent);
}
if(child.getType() == IDs::hierarchyMappingNode)
@@ -81,7 +103,9 @@ namespace AK::WwiseTransfer::ApplicationState
{
if(parent.getType() == IDs::hierarchyMapping)
{
- validateHierarchyMapping(parent);
+ const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]);
+
+ validateHierarchyMapping(importDestinationType, parent);
}
}
@@ -89,7 +113,9 @@ namespace AK::WwiseTransfer::ApplicationState
{
if(parent.getType() == IDs::hierarchyMapping)
{
- validateHierarchyMapping(parent);
+ const auto importDestinationType = juce::VariantConverter<Wwise::ObjectType>::fromVar(applicationState[IDs::importDestinationType]);
+
+ validateHierarchyMapping(importDestinationType, parent);
}
}
@@ -105,21 +131,15 @@ namespace AK::WwiseTransfer::ApplicationState
return originalsSubfolderAbsolutePath.isAChildOf(originalsFolderWithLanguageSubfolder);
}
- bool Validator::validateImportDestination(const juce::String& importDestination, Wwise::ObjectType objectType)
+ bool Validator::validateImportDestination(const juce::String& importDestination)
{
using namespace Wwise;
- static const std::initializer_list<ObjectType> allowedObjectTypes = {ObjectType::VirtualFolder, ObjectType::WorkUnit,
- ObjectType::RandomContainer, ObjectType::BlendContainer, ObjectType::ActorMixer, ObjectType::SwitchContainer};
-
static const juce::String pathPrefix = "\\Actor-Mixer Hierarchy";
- auto allowedType = std::find(allowedObjectTypes.begin(), allowedObjectTypes.end(),
- objectType) != allowedObjectTypes.end();
-
auto allowedPathPrefix = importDestination.startsWith(pathPrefix);
- return importDestination.isNotEmpty() && !importDestination.endsWith("\\") && allowedType && allowedPathPrefix;
+ return importDestination.isNotEmpty() && !importDestination.endsWith("\\") && allowedPathPrefix;
}
void Validator::validatePropertyTemplatePath(juce::ValueTree hierarchyMappingNode)
@@ -159,43 +179,39 @@ namespace AK::WwiseTransfer::ApplicationState
hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectNameErrorMessage, errorMessage, nullptr);
}
- void Validator::validateHierarchyMapping(juce::ValueTree hierarchyMapping)
+ void Validator::validateHierarchyMapping(Wwise::ObjectType importDestinationType, juce::ValueTree hierarchyMapping)
{
- // TODO: Should error be reported on parent or child?
- auto hierarchyMappingNodeList = ImportHelper::valueTreeToHierarchyMappingNodeList(hierarchyMapping);
+ // To properly validate the hierarchy mapping, we must include the import destination
+ std::vector<Wwise::ObjectType> hierarchyTypes{importDestinationType};
+ for(int i = 0; i < hierarchyMapping.getNumChildren(); ++i)
+ {
+ const auto hierarchyMappingNode = hierarchyMapping.getChild(i);
+ hierarchyTypes.emplace_back(juce::VariantConverter<Wwise::ObjectType>::fromVar(hierarchyMappingNode[IDs::objectType]));
+ }
- for(std::size_t i = 0; i < hierarchyMappingNodeList.size(); ++i)
+ for(std::size_t i = 1; i < hierarchyTypes.size(); ++i)
{
- auto& child = hierarchyMappingNodeList.at(i);
+ const auto child = hierarchyTypes[i];
+ const auto parent = hierarchyTypes[i - 1];
bool isValid = true;
juce::String errorMessage;
// Last item must be SoundSFX, report this error above any others
- if(i == hierarchyMappingNodeList.size() - 1)
+ if(i == hierarchyTypes.size() - 1 && child != Wwise::ObjectType::SoundSFX && child != Wwise::ObjectType::SoundVoice)
{
- if(child.type != Wwise::ObjectType::SoundSFX && child.type != Wwise::ObjectType::SoundVoice)
- {
- isValid = false;
- errorMessage << "Last item must be of type 'SoundSFX' or 'Sound Voice'";
- }
+ isValid = false;
+ errorMessage << "Last item must be of type 'SoundSFX' or 'Sound Voice'";
}
-
- // Since we have limited space in the tooltip, do not do any other validation if an error was already detected
- if(isValid && i != 0)
+ else if(parent != Wwise::ObjectType::Unknown &&
+ child != Wwise::ObjectType::Unknown &&
+ !WwiseHelper::validateObjectTypeParentChildRelationShip(parent, child))
{
- auto& parent = hierarchyMappingNodeList.at(i - 1);
-
- if(parent.type != Wwise::ObjectType::Unknown &&
- child.type != Wwise::ObjectType::Unknown &&
- !WwiseHelper::validateObjectTypeParentChildRelationShip(parent.type, child.type))
- {
- isValid = false;
- errorMessage << "'" << WwiseHelper::objectTypeToReadableString(child.type) << "' cannot be a child of '" << WwiseHelper::objectTypeToReadableString(parent.type) << "'";
- }
+ isValid = false;
+ errorMessage << "'" << WwiseHelper::objectTypeToReadableString(child) << "' cannot be a child of '" << WwiseHelper::objectTypeToReadableString(parent) << "'";
}
- auto hierarchyMappingNode = hierarchyMapping.getChild(i);
+ auto hierarchyMappingNode = hierarchyMapping.getChild(i - 1); // Index is off by 1 due to the importDestination
hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectTypeValid, isValid, nullptr);
hierarchyMappingNode.setPropertyExcludingListener(this, IDs::objectTypeErrorMessage, errorMessage, nullptr);
}
diff --git a/src/shared/Persistance/ApplicationStateValidator.h b/src/shared/Persistance/ApplicationStateValidator.h
@@ -1,6 +1,7 @@
#pragma once
#include "Core/DawContext.h"
+#include "Core/WaapiClient.h"
#include "Model/Wwise.h"
#include <juce_data_structures/juce_data_structures.h>
@@ -11,21 +12,22 @@ namespace AK::WwiseTransfer::ApplicationState
: juce::ValueTree::Listener
{
public:
- Validator(juce::ValueTree appState);
+ Validator(juce::ValueTree appState, WaapiClient& waapiClient);
~Validator();
private:
juce::ValueTree applicationState;
+ WaapiClient& waapiClient;
void valueTreePropertyChanged(juce::ValueTree& valueTree, const juce::Identifier& property) override;
void valueTreeChildAdded(juce::ValueTree& parent, juce::ValueTree& child) override;
void valueTreeChildRemoved(juce::ValueTree& parent, juce::ValueTree& child, int indexOfChild) override;
void valueTreeChildOrderChanged(juce::ValueTree& parent, int oldIndex, int newIndex) override;
- bool validateImportDestination(const juce::String& importDestination, Wwise::ObjectType objectType);
+ bool validateImportDestination(const juce::String& importDestination);
bool validateOriginalsSubfolder(const juce::String& originalsFolder, const juce::String& languageSubfolder, const juce::String& originalsSubfolder);
void validatePropertyTemplatePath(juce::ValueTree hierarchyMappingNode);
void validateObjectName(juce::ValueTree hierarchyMappingNode);
- void validateHierarchyMapping(juce::ValueTree hierarchyMapping);
+ void validateHierarchyMapping(Wwise::ObjectType importDestinationType, juce::ValueTree hierarchyMapping);
};
} // namespace AK::WwiseTransfer::ApplicationState
diff --git a/src/shared/Theme/CustomLookAndFeel.cpp b/src/shared/Theme/CustomLookAndFeel.cpp
@@ -14,6 +14,7 @@ namespace AK::WwiseTransfer
, highlightedFillColor{0xff646464}
, buttonBackgroundColor{0xff5a5a5a}
, thinOutlineColor{0xff292929}
+ , focusedOutlineColor{0xffbc9770}
, tableHeaderBackgroundColor{0xff545454}
, previewItemNoChangeColor{0xff7d7d7d}
, previewItemNewColor{0xff29afff}
@@ -35,6 +36,8 @@ namespace AK::WwiseTransfer
setColour(juce::TextButton::buttonColourId, buttonBackgroundColor);
setColour(juce::TextEditor::outlineColourId, thinOutlineColor);
+ setColour(juce::TextEditor::focusedOutlineColourId, focusedOutlineColor);
+ setColour(juce::ComboBox::focusedOutlineColourId, focusedOutlineColor);
setColour(juce::ComboBox::outlineColourId, thinOutlineColor);
setColour(juce::TreeView::backgroundColourId, widgetBackgroundColor);
setColour(juce::TableHeaderComponent::backgroundColourId, tableHeaderBackgroundColor);
@@ -45,7 +48,7 @@ namespace AK::WwiseTransfer
setColour(juce::TooltipWindow::backgroundColourId, widgetBackgroundColor);
}
- const std::shared_ptr<juce::Drawable>& CustomLookAndFeel::getIconForObjectType(Wwise::ObjectType objectType)
+ std::unique_ptr<juce::Drawable> CustomLookAndFeel::getIconForObjectType(Wwise::ObjectType objectType)
{
using namespace Wwise;
@@ -53,63 +56,51 @@ namespace AK::WwiseTransfer
{
case ObjectType::ActorMixer:
{
- static std::shared_ptr<juce::Drawable> actorMixerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_PhysicalFolder_nor_svg, BinaryData::ObjectIcons_PhysicalFolder_nor_svgSize));
- return actorMixerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_PhysicalFolder_nor_svg, BinaryData::ObjectIcons_PhysicalFolder_nor_svgSize);
}
case ObjectType::AudioFileSource:
{
- static std::shared_ptr<juce::Drawable> actorMixerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_AudioObjectSound_nor_svg, BinaryData::ObjectIcons_AudioObjectSound_nor_svgSize));
- return actorMixerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_AudioObjectSound_nor_svg, BinaryData::ObjectIcons_AudioObjectSound_nor_svgSize);
}
case ObjectType::BlendContainer:
{
- static std::shared_ptr<juce::Drawable> blendContainerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_BlendContainer_nor_svg, BinaryData::ObjectIcons_BlendContainer_nor_svgSize));
- return blendContainerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_BlendContainer_nor_svg, BinaryData::ObjectIcons_BlendContainer_nor_svgSize);
}
case ObjectType::PhysicalFolder:
{
- static std::shared_ptr<juce::Drawable> physicalFolderIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_PhysicalFolder_nor_svg, BinaryData::ObjectIcons_PhysicalFolder_nor_svgSize));
- return physicalFolderIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_PhysicalFolder_nor_svg, BinaryData::ObjectIcons_PhysicalFolder_nor_svgSize);
}
case ObjectType::RandomContainer:
{
- static std::shared_ptr<juce::Drawable> randomContainerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_RandomContainer_nor_svg, BinaryData::ObjectIcons_RandomContainer_nor_svgSize));
- return randomContainerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_RandomContainer_nor_svg, BinaryData::ObjectIcons_RandomContainer_nor_svgSize);
}
case ObjectType::SequenceContainer:
{
- static std::shared_ptr<juce::Drawable> sequenceContainerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SequenceContainer_nor_svg, BinaryData::ObjectIcons_SequenceContainer_nor_svgSize));
- return sequenceContainerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SequenceContainer_nor_svg, BinaryData::ObjectIcons_SequenceContainer_nor_svgSize);
}
case ObjectType::SoundSFX:
{
- static std::shared_ptr<juce::Drawable> soundSFXIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SoundFX_nor_svg, BinaryData::ObjectIcons_SoundFX_nor_svgSize));
- return soundSFXIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SoundFX_nor_svg, BinaryData::ObjectIcons_SoundFX_nor_svgSize);
}
case ObjectType::SoundVoice:
{
- static std::shared_ptr<juce::Drawable> soundVoiceIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SoundVoice_nor_svg, BinaryData::ObjectIcons_SoundVoice_nor_svgSize));
- return soundVoiceIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SoundVoice_nor_svg, BinaryData::ObjectIcons_SoundVoice_nor_svgSize);
}
case ObjectType::SwitchContainer:
{
- static std::shared_ptr<juce::Drawable> switchContainerIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SwitchContainer_nor_svg, BinaryData::ObjectIcons_SwitchContainer_nor_svgSize));
- return switchContainerIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_SwitchContainer_nor_svg, BinaryData::ObjectIcons_SwitchContainer_nor_svgSize);
}
case ObjectType::VirtualFolder:
{
- static std::shared_ptr<juce::Drawable> virtualFolderIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Folder_nor_svg, BinaryData::ObjectIcons_Folder_nor_svgSize));
- return virtualFolderIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Folder_nor_svg, BinaryData::ObjectIcons_Folder_nor_svgSize);
}
case ObjectType::WorkUnit:
{
- static std::shared_ptr<juce::Drawable> workUnitIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Workunit_nor_svg, BinaryData::ObjectIcons_Workunit_nor_svgSize));
- return workUnitIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Workunit_nor_svg, BinaryData::ObjectIcons_Workunit_nor_svgSize);
}
default:
{
- static std::shared_ptr<juce::Drawable> defaultIcon(juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Folder_nor_svg, BinaryData::ObjectIcons_Folder_nor_svgSize));
- return defaultIcon;
+ return juce::Drawable::createFromImageData(BinaryData::ObjectIcons_Folder_nor_svg, BinaryData::ObjectIcons_Folder_nor_svgSize);
}
}
}
@@ -218,28 +209,27 @@ namespace AK::WwiseTransfer
juce::Justification::centred, true);
}
- juce::Typeface::Ptr CustomLookAndFeel::getTypefaceForFont(const juce::Font& font)
- {
- if(font.isBold())
- return boldTypeFace;
-
- return regularTypeFace;
- }
-
void CustomLookAndFeel::drawTextEditorOutline(juce::Graphics& g, int width, int height, juce::TextEditor& textEditor)
{
- if(dynamic_cast<juce::AlertWindow*>(textEditor.getParentComponent()) != nullptr || !textEditor.isEnabled() || textEditor.isReadOnly())
+ if(dynamic_cast<juce::AlertWindow*>(textEditor.getParentComponent()) != nullptr)
return;
const auto hasKeyboardFocus = textEditor.hasKeyboardFocus(true);
auto colour = textEditor.findColour(hasKeyboardFocus ? juce::TextEditor::focusedOutlineColourId : juce::TextEditor::outlineColourId);
- auto lineThickness = hasKeyboardFocus ? 2 : 1;
+ auto lineThickness = 1;
g.setColour(colour);
g.drawRect(0, 0, width, height, lineThickness);
}
+ void CustomLookAndFeel::fillTextEditorBackground(juce::Graphics& g, int width, int height, juce::TextEditor& textEditor)
+ {
+ auto backgroundColor = textEditor.isReadOnly() ? windowBackgroundColor : textEditor.findColour(juce::TextEditor::backgroundColourId);
+
+ g.fillAll(backgroundColor);
+ }
+
void CustomLookAndFeel::drawTooltip(juce::Graphics& g, const juce::String& text, int width, int height)
{
juce::Rectangle<int> bounds(width, height);
@@ -263,6 +253,42 @@ namespace AK::WwiseTransfer
tl.draw(g, {static_cast<float>(width), static_cast<float>(height)});
}
+ void CustomLookAndFeel::drawComboBox(juce::Graphics& g, int width, int height, bool isButtonDown,
+ int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox& box)
+ {
+ // Copied from juce_LookAndFeel_V4.cpp and added support for outlining the component when in focus
+
+ auto cornerSize = box.findParentComponentOfClass<juce::ChoicePropertyComponent>() != nullptr ? 0.0f : 3.0f;
+ juce::Rectangle<int> boxBounds(0, 0, width, height);
+
+ g.setColour(box.findColour(juce::ComboBox::backgroundColourId));
+ g.fillRoundedRectangle(boxBounds.toFloat(), cornerSize);
+
+ if(box.isEnabled() && box.hasKeyboardFocus(false))
+ g.setColour(box.findColour(juce::ComboBox::focusedOutlineColourId));
+ else
+ g.setColour(box.findColour(juce::ComboBox::outlineColourId));
+
+ g.drawRoundedRectangle(boxBounds.toFloat().reduced(0.5f, 0.5f), cornerSize, 1.0f);
+
+ juce::Rectangle<int> arrowZone(width - 30, 0, 20, height);
+ juce::Path path;
+ path.startNewSubPath((float)arrowZone.getX() + 3.0f, (float)arrowZone.getCentreY() - 2.0f);
+ path.lineTo((float)arrowZone.getCentreX(), (float)arrowZone.getCentreY() + 3.0f);
+ path.lineTo((float)arrowZone.getRight() - 3.0f, (float)arrowZone.getCentreY() - 2.0f);
+
+ g.setColour(box.findColour(juce::ComboBox::arrowColourId).withAlpha((box.isEnabled() ? 0.9f : 0.2f)));
+ g.strokePath(path, juce::PathStrokeType(2.0f));
+ }
+
+ juce::Typeface::Ptr CustomLookAndFeel::getTypefaceForFont(const juce::Font& font)
+ {
+ if(font.isBold())
+ return boldTypeFace;
+
+ return regularTypeFace;
+ }
+
juce::Font CustomLookAndFeel::getLabelFont(juce::Label& label)
{
static auto defaultLabelFont = juce::Label().getFont();
diff --git a/src/shared/Theme/CustomLookAndFeel.h b/src/shared/Theme/CustomLookAndFeel.h
@@ -20,11 +20,13 @@ namespace AK::WwiseTransfer
public:
CustomLookAndFeel();
- const std::shared_ptr<juce::Drawable>& getIconForObjectType(Wwise::ObjectType objectType);
+ std::unique_ptr<juce::Drawable> getIconForObjectType(Wwise::ObjectType objectType);
juce::Colour getTextColourForObjectStatus(Import::ObjectStatus objectStatus);
void drawTextEditorOutline(juce::Graphics& g, int width, int height, juce::TextEditor& textEditor) override;
+ void fillTextEditorBackground(juce::Graphics& g, int width, int height, juce::TextEditor& textEditor) override;
+
void drawTableHeaderColumn(juce::Graphics& g, juce::TableHeaderComponent& header,
const juce::String& columnName, int /*columnId*/,
int width, int height, bool isMouseOver, bool isMouseDown,
@@ -35,6 +37,9 @@ namespace AK::WwiseTransfer
void drawTooltip(juce::Graphics&, const juce::String& text, int w, int h) override;
+ void drawComboBox(juce::Graphics& g, int width, int height, bool isButtonDown, int buttonX,
+ int buttonY, int buttonW, int buttonH, juce::ComboBox& box) override;
+
juce::Typeface::Ptr getTypefaceForFont(const juce::Font&) override;
juce::Font getLabelFont(juce::Label& label) override;
@@ -58,6 +63,7 @@ namespace AK::WwiseTransfer
juce::Colour highlightedFillColor;
juce::Colour buttonBackgroundColor;
juce::Colour thinOutlineColor;
+ juce::Colour focusedOutlineColor;
juce::Colour tableHeaderBackgroundColor;
juce::Colour previewItemNoChangeColor;
juce::Colour previewItemNewColor;
diff --git a/src/shared/UI/AboutComponent.h b/src/shared/UI/AboutComponent.h
@@ -31,5 +31,7 @@ namespace AK::WwiseTransfer
juce::TooltipWindow tooltipWindow;
std::unique_ptr<juce::Drawable> wwiseIcon;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AboutComponent)
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/CustomDrawableButton.h b/src/shared/UI/CustomDrawableButton.h
@@ -14,5 +14,7 @@ namespace AK::WwiseTransfer
private:
std::unique_ptr<juce::Drawable> drawable;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CustomDrawableButton)
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/ImportConflictsComponent.cpp b/src/shared/UI/ImportConflictsComponent.cpp
@@ -20,7 +20,6 @@ namespace AK::WwiseTransfer
ImportConflictsComponent::ImportConflictsComponent(juce::ValueTree appState)
: applicationState(appState)
, containerNameExists(applicationState, IDs::containerNameExists, nullptr)
- , audioFilenameExists(applicationState, IDs::audioFileNameExists, nullptr)
, applyTemplate(applicationState, IDs::applyTemplate, nullptr)
{
using namespace ImportConflictsComponentContants;
@@ -45,7 +44,6 @@ namespace AK::WwiseTransfer
applyTemplateComboBox.addItem(ImportHelper::applyTemplateOptionToReadableString(applyTemplateOption), static_cast<int>(applyTemplateOption));
containerNameExistsComboBox.getSelectedIdAsValue().referTo(containerNameExists.getPropertyAsValue());
- audioFileNameExistsComboBox.getSelectedIdAsValue().referTo(audioFilenameExists.getPropertyAsValue());
applyTemplateComboBox.getSelectedIdAsValue().referTo(applyTemplate.getPropertyAsValue());
auto containerNameExistsComboBoxOnChange = [this]
@@ -53,27 +51,27 @@ namespace AK::WwiseTransfer
const char* tooltip = "";
switch(static_cast<Import::ContainerNameExistsOption>(containerNameExistsComboBox.getSelectedId()))
{
- case Import::ContainerNameExistsOption::UseExisting:
- tooltip = "Retains the existing sounds but adds the transferred files as source audio "
- "files. The new files do not become the main audio sources for the existing "
- "objects unless they have identical names to the existing sources, and "
- "therefore overwrite them.";
- break;
-
- case Import::ContainerNameExistsOption::CreateNew:
- tooltip = "Creates new sounds with increments appended to the names, for example "
- "SoundName_01. The existing sounds are not affected, unless the existing audio "
- "sources have the same names as the newly transferred files. In that case, the "
- "new sources overwrite the existing ones.";
- break;
-
- case Import::ContainerNameExistsOption::Replace:
- tooltip = "Deletes and recreates the sounds, with the transferred files as sources.";
- break;
-
- default:
- jassertfalse;
- break;
+ case Import::ContainerNameExistsOption::UseExisting:
+ tooltip = "Retains the existing sounds but adds the transferred files as source audio "
+ "files. The new files do not become the main audio sources for the existing "
+ "objects unless they have identical names to the existing sources, and "
+ "therefore overwrite them.";
+ break;
+
+ case Import::ContainerNameExistsOption::CreateNew:
+ tooltip = "Creates new sounds with increments appended to the names, for example "
+ "SoundName_01. The existing sounds are not affected, unless the existing audio "
+ "sources have the same names as the newly transferred files. In that case, the "
+ "new sources overwrite the existing ones.";
+ break;
+
+ case Import::ContainerNameExistsOption::Replace:
+ tooltip = "Deletes and recreates the sounds, with the transferred files as sources.";
+ break;
+
+ default:
+ jassertfalse;
+ break;
}
containerNameExistsComboBox.setTooltip(tooltip);
@@ -83,10 +81,8 @@ namespace AK::WwiseTransfer
containerNameExistsComboBox.onChange = containerNameExistsComboBoxOnChange;
addAndMakeVisible(containerNameExistsLabel);
- addAndMakeVisible(audioFileNameExistsLabel);
addAndMakeVisible(applyTemplateLabel);
addAndMakeVisible(containerNameExistsComboBox);
- addAndMakeVisible(audioFileNameExistsComboBox);
addAndMakeVisible(applyTemplateComboBox);
refreshComponent();
diff --git a/src/shared/UI/ImportConflictsComponent.h b/src/shared/UI/ImportConflictsComponent.h
@@ -19,11 +19,9 @@ namespace AK::WwiseTransfer
private:
juce::Label containerNameExistsLabel;
- juce::Label audioFileNameExistsLabel;
juce::Label applyTemplateLabel;
juce::ComboBox containerNameExistsComboBox;
- juce::ComboBox audioFileNameExistsComboBox;
juce::ComboBox applyTemplateComboBox;
juce::ValueTree applicationState;
@@ -35,10 +33,10 @@ namespace AK::WwiseTransfer
juce::CachedValue<juce::String> selectObjectsOnImportCommand;
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImportConflictsComponent);
-
void valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property) override;
void handleAsyncUpdate() override;
void refreshComponent();
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImportConflictsComponent);
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/ImportControlsComponent.cpp b/src/shared/UI/ImportControlsComponent.cpp
@@ -32,6 +32,7 @@ namespace AK::WwiseTransfer
, containerNameExistsOption(applicationState, IDs::containerNameExists, nullptr)
, applyTemplateOption(applicationState, IDs::applyTemplate, nullptr)
, hierarchyMapping(applicationState.getChildWithName(IDs::hierarchyMapping))
+ , previewItems(applicationState.getChildWithName(IDs::previewItems))
, waapiClient(waapiClient)
, dawContext(dawContext)
, applicationProperties(applicationProperties)
@@ -49,7 +50,7 @@ namespace AK::WwiseTransfer
importButton.onClick = [this]
{
- onImportButtonClick();
+ transferToWwise();
};
addAndMakeVisible(importButton);
@@ -90,8 +91,11 @@ namespace AK::WwiseTransfer
}
} // namespace
- void ImportControlsComponent::onImportButtonClick()
+ void ImportControlsComponent::transferToWwise()
{
+ if(!importButton.isEnabled())
+ return;
+
using namespace ImportControlsComponentConstants;
// Disable the import button while rendering
@@ -115,7 +119,7 @@ namespace AK::WwiseTransfer
{
const juce::String message("One or more files failed to render.");
juce::Logger::writeToLog(message);
- juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Import Aborted", message);
+ juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Transfer to Wwise Aborted", message);
importButton.setEnabled(true);
return;
}
@@ -134,7 +138,7 @@ namespace AK::WwiseTransfer
break;
}
- if(importItem.audioFilePath != importItem.renderFilePath)
+ if(juce::File(importItem.audioFilePath) != juce::File(importItem.renderFilePath))
{
showRenameWarning = true;
break;
@@ -152,7 +156,7 @@ namespace AK::WwiseTransfer
{
const juce::String message("One or more files failed to render.");
juce::Logger::writeToLog(message);
- juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Import Aborted", message);
+ juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Transfer to Wwise Aborted", message);
importButton.setEnabled(true);
return;
}
@@ -236,7 +240,7 @@ namespace AK::WwiseTransfer
message << juce::NewLine() << summary.errorMessage;
auto messageBoxOptions = juce::MessageBoxOptions()
- .withTitle("Import Summary")
+ .withTitle("Wwise Import Summary")
.withMessage(message)
.withButton("View Details")
.withButton("Close");
@@ -257,7 +261,7 @@ namespace AK::WwiseTransfer
auto currentTime = juce::Time::getCurrentTime();
auto importSummaryFile = juce::File::getSpecialLocation(juce::File::tempDirectory)
- .getChildFile(applicationName + "_ImportSummary_" + currentTime.formatted("%Y-%m-%d_%H-%M-%S"))
+ .getChildFile(applicationName + "_WwiseImportSummary_" + currentTime.formatted("%Y-%m-%d_%H-%M-%S"))
.withFileExtension(".html");
importSummaryFile.create();
@@ -265,7 +269,7 @@ namespace AK::WwiseTransfer
importSummaryFile.appendText("<style>table td, th { border:1px solid black; padding:10px; }");
importSummaryFile.appendText("table { border-collapse:collapse; }");
importSummaryFile.appendText("table th { text-align: left; }</style>");
- importSummaryFile.appendText("<pre>" + applicationName + ": Import Summary " + currentTime.formatted("%Y-%m-%d %H:%M:%S") + "\n\n");
+ importSummaryFile.appendText("<pre>" + applicationName + ": Wwise Import Summary " + currentTime.formatted("%Y-%m-%d %H:%M:%S") + "\n\n");
importSummaryFile.appendText("Import Destination: " + importTaskOptions.importDestination + "\n");
importSummaryFile.appendText("Container Name Exists: " + ImportHelper::containerNameExistsOptionToReadableString(importTaskOptions.containerNameExistsOption) + "\n");
importSummaryFile.appendText("Apply Template: " + ImportHelper::applyTemplateOptionToReadableString(importTaskOptions.applyTemplateOption) + "\n\n");
@@ -291,7 +295,7 @@ namespace AK::WwiseTransfer
void ImportControlsComponent::refreshComponent()
{
- auto importButtonEnabled = originalsSubfolderValid.get() && importDestinationValid.get() && projectPath.get().isNotEmpty();
+ auto importButtonEnabled = originalsSubfolderValid.get() && importDestinationValid.get() && projectPath.get().isNotEmpty() && previewItems.getNumChildren() > 0;
auto hieararchyMappingNodes = ImportHelper::valueTreeToHierarchyMappingNodeList(hierarchyMapping);
@@ -307,10 +311,12 @@ namespace AK::WwiseTransfer
importButton.setEnabled(importButtonEnabled);
- juce::String tooltip = "";
+ juce::String tooltip;
if(projectPath.get().isEmpty())
tooltip = "Connect to Wwise to continue";
+ else if(previewItems.getNumChildren() == 0)
+ tooltip = "Nothing to transfer";
else if(!importButtonEnabled)
tooltip = "Fix pending errors to continue";
@@ -328,7 +334,7 @@ namespace AK::WwiseTransfer
void ImportControlsComponent::valueTreeChildAdded(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenAdded)
{
- if(parentTree.getType() == IDs::hierarchyMapping)
+ if(parentTree.getType() == IDs::hierarchyMapping || parentTree.getType() == IDs::previewItems)
{
triggerAsyncUpdate();
}
@@ -336,7 +342,7 @@ namespace AK::WwiseTransfer
void ImportControlsComponent::valueTreeChildRemoved(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved)
{
- if(parentTree.getType() == IDs::hierarchyMapping)
+ if(parentTree.getType() == IDs::hierarchyMapping || parentTree.getType() == IDs::previewItems)
{
triggerAsyncUpdate();
}
diff --git a/src/shared/UI/ImportControlsComponent.h b/src/shared/UI/ImportControlsComponent.h
@@ -20,6 +20,8 @@ namespace AK::WwiseTransfer
void resized() override;
+ void transferToWwise();
+
private:
juce::TextButton importButton;
juce::ValueTree applicationState;
@@ -33,6 +35,7 @@ namespace AK::WwiseTransfer
juce::CachedValue<Import::ContainerNameExistsOption> containerNameExistsOption;
juce::CachedValue<Import::ApplyTemplateOption> applyTemplateOption;
juce::ValueTree hierarchyMapping;
+ juce::ValueTree previewItems;
juce::CachedValue<juce::String> selectObjectsOnImportCommand;
juce::CachedValue<bool> applyTemplateFeatureEnabled;
@@ -49,7 +52,6 @@ namespace AK::WwiseTransfer
const juce::String applicationName;
- void onImportButtonClick();
void showImportSummary(const Import::Summary& summary, const Import::Task::Options& importTaskOptions);
void viewImportSummaryDetails(const Import::Summary& summary, const Import::Task::Options& importTaskOptions);
diff --git a/src/shared/UI/ImportDestinationComponent.cpp b/src/shared/UI/ImportDestinationComponent.cpp
@@ -13,6 +13,7 @@ namespace AK::WwiseTransfer
constexpr int editorBoxHeight = 26;
constexpr int labelWidth = 120;
constexpr int syncButtonWidth = 36;
+ constexpr int iconSize = 16;
} // namespace ImportDestinationComponentConstants
ImportDestinationComponent::ImportDestinationComponent(juce::ValueTree appState, WaapiClient& waapiClient)
@@ -45,6 +46,7 @@ namespace AK::WwiseTransfer
addAndMakeVisible(importDestinationLabel);
addAndMakeVisible(importDestinationEditor);
addAndMakeVisible(updateImportDestinationButton);
+ addAndMakeVisible(objectTypeIconComposite);
refreshComponent();
}
@@ -60,15 +62,19 @@ namespace AK::WwiseTransfer
auto area = getLocalBounds();
- auto ImportDestinationSection = area.removeFromTop(editorBoxHeight);
+ auto importDestinationSection = area.removeFromTop(editorBoxHeight);
{
- importDestinationLabel.setBounds(ImportDestinationSection.removeFromLeft(labelWidth));
- ImportDestinationSection.removeFromLeft(margin);
+ importDestinationLabel.setBounds(importDestinationSection.removeFromLeft(labelWidth));
+ importDestinationSection.removeFromLeft(margin);
- updateImportDestinationButton.setBounds(ImportDestinationSection.removeFromRight(syncButtonWidth));
- ImportDestinationSection.removeFromRight(spacing);
+ updateImportDestinationButton.setBounds(importDestinationSection.removeFromRight(syncButtonWidth));
+ importDestinationSection.removeFromRight(spacing);
- importDestinationEditor.setBounds(ImportDestinationSection);
+ objectTypeIconComposite.setBounds(importDestinationSection.removeFromRight(iconSize).withSizeKeepingCentre(iconSize, iconSize));
+
+ importDestinationSection.removeFromRight(spacing);
+
+ importDestinationEditor.setBounds(importDestinationSection);
}
}
@@ -77,6 +83,16 @@ namespace AK::WwiseTransfer
auto projectPathEmpty = projectPath.get().isEmpty();
updateImportDestinationButton.setEnabled(!projectPathEmpty);
+
+ auto* customLookAndFeel = dynamic_cast<CustomLookAndFeel*>(&getLookAndFeel());
+
+ if(customLookAndFeel)
+ {
+ // Reset the icon and add it as a child of the composite. The composite will refresh the icon automatically
+ objectTypeIconComposite.removeAllChildren();
+ objectTypeIcon = customLookAndFeel->getIconForObjectType(importDestinationType);
+ objectTypeIconComposite.addAndMakeVisible(objectTypeIcon.get());
+ }
}
void ImportDestinationComponent::updateImportDestination()
@@ -87,7 +103,6 @@ namespace AK::WwiseTransfer
return juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Import Destination", "No object is selected in Wwise. Please select one and try again.");
importDestination = response.result.path;
- importDestinationType = response.result.type;
};
waapiClient.getSelectedObjectAsync(onGetSelectedObject);
diff --git a/src/shared/UI/ImportDestinationComponent.h b/src/shared/UI/ImportDestinationComponent.h
@@ -36,6 +36,9 @@ namespace AK::WwiseTransfer
void valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged, const juce::Identifier& property) override;
void handleAsyncUpdate() override;
+ juce::DrawableComposite objectTypeIconComposite;
+ std::unique_ptr<juce::Drawable> objectTypeIcon;
+
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImportDestinationComponent);
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/ImportPreviewComponent.h b/src/shared/UI/ImportPreviewComponent.h
@@ -83,10 +83,10 @@ namespace AK::WwiseTransfer
void valueTreeChildAdded(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenAdded);
void valueTreeChildRemoved(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved);
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImportPreviewComponent)
-
// Inherited via AsyncUpdater
void handleAsyncUpdate() override;
void refreshHeader();
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImportPreviewComponent)
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/LoadingComponent.h b/src/shared/UI/LoadingComponent.h
@@ -17,6 +17,7 @@ namespace AK::WwiseTransfer
juce::Label text;
double progress = -1;
+
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LoadingComponent)
};
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/MainComponent.cpp b/src/shared/UI/MainComponent.cpp
@@ -23,7 +23,7 @@ namespace AK::WwiseTransfer
MainComponent::MainComponent(DawContext& dawContext, const juce::String& applicationName)
: applicationState(ApplicationState::create())
- , validator(applicationState)
+ , validator(applicationState, waapiClient)
, applicationProperties(applicationName)
, waapiClientWatcher(applicationState, waapiClient, WaapiClientWatcherConfig{applicationProperties.getWaapiIp(), applicationProperties.getWaapiPort(), MainComponentConstants::connectionMonitorDelayDefault, MainComponentConstants::minConnectionRetryDelayDefault, MainComponentConstants::maxConnectionRetryDelayDefault})
, originalsSubfolderComponent(applicationState, applicationName)
@@ -143,4 +143,9 @@ namespace AK::WwiseTransfer
{
return applicationProperties.getScaleFactorOverride() > 0.0;
}
+
+ void MainComponent::transferToWwise()
+ {
+ importControlsComponent.transferToWwise();
+ }
} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/MainComponent.h b/src/shared/UI/MainComponent.h
@@ -35,12 +35,14 @@ namespace AK::WwiseTransfer
void resized() override;
bool hasScaleFactorOverride();
+ void transferToWwise();
+
private:
juce::ValueTree applicationState;
+ WaapiClient waapiClient;
ApplicationState::Validator validator;
ApplicationProperties applicationProperties;
- WaapiClient waapiClient;
WaapiClientWatcher waapiClientWatcher;
Logger logger;
DawWatcher dawWatcher;
diff --git a/src/shared/UI/MainWindow.cpp b/src/shared/UI/MainWindow.cpp
@@ -0,0 +1,66 @@
+#include "MainWindow.h"
+
+#include <limits>
+
+namespace AK::WwiseTransfer
+{
+ namespace ExtensionWindowConstants
+ {
+ constexpr int width = 600;
+ constexpr int height = 800;
+ constexpr int minWidth = 420;
+ constexpr int minHeight = 650;
+ constexpr int standardDPI = 96;
+ } // namespace ExtensionWindowConstants
+
+ MainWindow::MainWindow(WwiseTransfer::DawContext& dawContext, const juce::String& applicationName, bool addToDesktop)
+ : juce::ResizableWindow(applicationName, addToDesktop)
+ {
+ using namespace ExtensionWindowConstants;
+
+ juce::LookAndFeel::setDefaultLookAndFeel(&lookAndFeel);
+
+ auto mainComponent = new WwiseTransfer::MainComponent(dawContext, applicationName);
+
+#ifdef WIN32
+ if(!mainComponent->hasScaleFactorOverride())
+ {
+ auto scaleFactor = juce::Desktop::getInstance().getDisplays().getMainDisplay().dpi / standardDPI;
+ juce::Desktop::getInstance().setGlobalScaleFactor(scaleFactor);
+ }
+#endif
+
+ setContentOwned(mainComponent, true);
+ centreWithSize(width, height);
+ setResizable(true, true);
+ setResizeLimits(minWidth, minHeight, (std::numeric_limits<int>::max)(), (std::numeric_limits<int>::max)());
+ }
+
+ MainWindow::~MainWindow()
+ {
+ juce::LookAndFeel::setDefaultLookAndFeel(nullptr);
+ }
+
+ int MainWindow::getDesktopWindowStyleFlags() const
+ {
+ return juce::ComponentPeer::windowHasCloseButton | juce::ComponentPeer::windowHasTitleBar |
+ juce::ComponentPeer::windowIsResizable | juce::ComponentPeer::windowHasMinimiseButton |
+ juce::ComponentPeer::windowAppearsOnTaskbar | juce::ComponentPeer::windowHasMaximiseButton;
+ }
+
+ void MainWindow::userTriedToCloseWindow()
+ {
+ setVisible(false);
+ }
+
+ void MainWindow::transferToWwise()
+ {
+ auto contentComponent = getContentComponent();
+ if(contentComponent != nullptr)
+ {
+ auto mainComponent = dynamic_cast<MainComponent*>(contentComponent);
+ if(mainComponent != nullptr)
+ mainComponent->transferToWwise();
+ }
+ }
+} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/MainWindow.h b/src/shared/UI/MainWindow.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "Core/DawContext.h"
+#include "MainComponent.h"
+#include "Theme/CustomLookAndFeel.h"
+
+namespace AK::WwiseTransfer
+{
+ class MainWindow : public juce::ResizableWindow
+ {
+ public:
+ MainWindow(WwiseTransfer::DawContext& dawContext, const juce::String& applicationName, bool addToDesktop);
+ ~MainWindow() override;
+
+ int getDesktopWindowStyleFlags() const override;
+ void userTriedToCloseWindow() override;
+
+ void transferToWwise();
+
+ private:
+ WwiseTransfer::CustomLookAndFeel lookAndFeel;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
+ };
+} // namespace AK::WwiseTransfer
diff --git a/src/shared/UI/OriginalsSubfolderComponent.cpp b/src/shared/UI/OriginalsSubfolderComponent.cpp
@@ -47,12 +47,12 @@ namespace AK::WwiseTransfer
originalsSubfolderEditor.getValidationValue().referTo(applicationState.getPropertyAsValue(IDs::originalsSubfolderValid, nullptr));
originalsSubfolderEditor.getErrorMessageValue().referTo(applicationState.getPropertyAsValue(IDs::originalsSubfolderErrorMessage, nullptr));
+ aboutButton.setTooltip("About ReaWwise");
aboutButton.onClick = [this]
{
showAboutWindow();
};
- fileBrowserButton.setButtonText("^");
fileBrowserButton.onClick = [this]
{
selectOriginalsSubfoler();
@@ -121,9 +121,10 @@ namespace AK::WwiseTransfer
auto originalsFolderEmpty = originalsFolder.get().isEmpty();
projectPathLabel.setEnabled(!projectPathEmpty);
+ projectPathEditor.setAlpha(!projectPathEmpty ? 1 : 0.5f);
fileBrowserButton.setEnabled(!projectPathEmpty && !originalsFolderEmpty);
- fileBrowserButton.setTooltip(originalsFolderEmpty ? "File browser is only available when connected to Wwise 2022+" : "");
+ fileBrowserButton.setTooltip(originalsFolderEmpty ? "File browser is only available when connected to Wwise 2022+" : "Browse");
}
void OriginalsSubfolderComponent::selectOriginalsSubfoler()
diff --git a/src/shared/UI/PresetMenuComponent.cpp b/src/shared/UI/PresetMenuComponent.cpp
@@ -9,9 +9,9 @@ namespace AK::WwiseTransfer
namespace PresetMenuComponentConstants
{
constexpr auto loadFlags = juce::FileBrowserComponent::openMode |
- juce::FileBrowserComponent::canSelectFiles;
+ juce::FileBrowserComponent::canSelectFiles;
constexpr auto saveFlags = juce::FileBrowserComponent::saveMode |
- juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting;
+ juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting;
constexpr auto fileFilter = "*.xml";
} // namespace PresetMenuComponentConstants
@@ -24,6 +24,8 @@ namespace AK::WwiseTransfer
if(!presetFolder.exists())
presetFolder.createDirectory();
+ setTooltip("Manage Presets");
+
onClick = [this]
{
showMenu();
@@ -44,11 +46,21 @@ namespace AK::WwiseTransfer
auto onFileChosen = [this](const juce::FileChooser& chooser)
{
auto file = chooser.getResult();
- file.create();
- const auto presetData = PersistanceHelper::hierarchyMappingToPresetData(hierarchyMapping);
- file.replaceWithText(presetData);
- applicationProperties.addRecentHierarchyMappingPreset(file.getFullPathName());
+ auto createResult = file.create();
+
+ if(createResult)
+ {
+ const auto presetData = PersistanceHelper::hierarchyMappingToPresetData(hierarchyMapping);
+ file.replaceWithText(presetData);
+ applicationProperties.addRecentHierarchyMappingPreset(file.getFullPathName());
+ }
+ // An empty path indicates that the file is invalid. This is the behavior for when the user clicks cancel in the file chooser
+ // https://docs.juce.com/master/classFile.html#a38506545c1402119b5fef7bcf6289fa9
+ else if(!file.getFullPathName().isEmpty())
+ {
+ juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Save Preset", createResult.getErrorMessage());
+ }
};
fileChooser->launchAsync(saveFlags, onFileChosen);
@@ -63,7 +75,7 @@ namespace AK::WwiseTransfer
auto onFileChosen = [this](const juce::FileChooser& chooser)
{
auto presetFile = chooser.getResult();
- if (presetFile.exists())
+ if(presetFile.exists())
loadPreset(presetFile);
};
diff --git a/src/shared/UI/SelectedRowPropertiesComponent.cpp b/src/shared/UI/SelectedRowPropertiesComponent.cpp
@@ -20,7 +20,7 @@ namespace AK::WwiseTransfer
constexpr int syncButtonWidth = 36;
constexpr int propertyTemplateToggleButtonWidth = 22;
constexpr int lineThickness = 2;
- constexpr std::initializer_list<Wwise::ObjectType> objectTypes{Wwise::ObjectType::BlendContainer,
+ constexpr std::initializer_list<Wwise::ObjectType> objectTypes{Wwise::ObjectType::ActorMixer, Wwise::ObjectType::BlendContainer,
Wwise::ObjectType::PhysicalFolder, Wwise::ObjectType::RandomContainer, Wwise::ObjectType::SequenceContainer, Wwise::ObjectType::SoundSFX,
Wwise::ObjectType::SoundVoice, Wwise::ObjectType::SwitchContainer, Wwise::ObjectType::VirtualFolder, Wwise::ObjectType::WorkUnit};
const juce::String pastePropertiesToolTip = "Paste properties are only available when connected to Wwise 2022+";
@@ -158,7 +158,7 @@ namespace AK::WwiseTransfer
auto onGetSelectedObject = [this](const Waapi::Response<Waapi::ObjectResponse>& response)
{
if(response.result.path.isEmpty())
- return juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Import Destination", "No object is selected in Wwise. Please select one and try again.");
+ return juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Property Template Path", "No object is selected in Wwise. Please select one and try again.");
propertyTemplatePath = response.result.path;
propertyTemplatePathType = response.result.type;
@@ -185,7 +185,7 @@ namespace AK::WwiseTransfer
propertyTemplatePathLabel.setEnabled(applyTemplateFeatureEnabled);
propertyTemplatePathEditor.setTooltip(applyTemplateFeatureEnabled ? "" : pastePropertiesToolTip);
- propertyTemplatePathSyncButton.setTooltip(applyTemplateFeatureEnabled ? "" : pastePropertiesToolTip);
+ propertyTemplatePathSyncButton.setTooltip(applyTemplateFeatureEnabled ? "Sync with selected object in Wwise" : pastePropertiesToolTip);
propertyTemplateToggleButton.setTooltip(applyTemplateFeatureEnabled ? "" : pastePropertiesToolTip);
propertyTemplatePathLabel.setTooltip(applyTemplateFeatureEnabled ? "" : pastePropertiesToolTip);
}
diff --git a/src/shared/UI/TruncatableTextEditor.h b/src/shared/UI/TruncatableTextEditor.h
@@ -22,5 +22,7 @@ namespace AK::WwiseTransfer
juce::String lastValue;
void resetText();
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TruncatableTextEditor);
};
} // namespace AK::WwiseTransfer
diff --git a/src/standalone/Standalone.cpp b/src/standalone/Standalone.cpp
@@ -4,31 +4,32 @@
namespace AK::WwiseTransfer
{
- const juce::String ReaperWwiseTransferStandalone::getApplicationName()
+ const juce::String Standalone::getApplicationName()
{
return JUCE_APPLICATION_NAME_STRING;
}
- const juce::String ReaperWwiseTransferStandalone::getApplicationVersion()
+ const juce::String Standalone::getApplicationVersion()
{
return JUCE_APPLICATION_VERSION_STRING;
}
- bool ReaperWwiseTransferStandalone::moreThanOneInstanceAllowed()
+ bool Standalone::moreThanOneInstanceAllowed()
{
return false;
}
- void ReaperWwiseTransferStandalone::initialise(const juce::String& commandLine)
+ void Standalone::initialise(const juce::String& commandLine)
{
juce::ignoreUnused(commandLine);
mainWindow.reset(new StandaloneWindow());
+ mainWindow->setVisible(true);
}
- void ReaperWwiseTransferStandalone::shutdown()
+ void Standalone::shutdown()
{
mainWindow.reset();
}
- START_JUCE_APPLICATION(ReaperWwiseTransferStandalone)
+ START_JUCE_APPLICATION(Standalone)
} // namespace AK::WwiseTransfer
diff --git a/src/standalone/Standalone.h b/src/standalone/Standalone.h
@@ -1,5 +1,7 @@
#pragma once
+#include "StubContext.h"
+
#include <juce_gui_basics/juce_gui_basics.h>
#include <memory>
@@ -7,7 +9,7 @@ namespace AK::WwiseTransfer
{
class StandaloneWindow;
- class ReaperWwiseTransferStandalone : public juce::JUCEApplication
+ class Standalone : public juce::JUCEApplication
{
public:
const juce::String getApplicationName() override;
diff --git a/src/standalone/StandaloneWindow.cpp b/src/standalone/StandaloneWindow.cpp
@@ -4,45 +4,19 @@
namespace AK::WwiseTransfer
{
- namespace StandaloneWindowConstants
- {
- constexpr int width = 600;
- constexpr int height = 800;
- constexpr int minWidth = 420;
- constexpr int minHeight = 650;
- } // namespace StandaloneWindowConstants
-
StandaloneWindow::StandaloneWindow()
- : juce::ResizableWindow(JUCE_APPLICATION_NAME_STRING, true)
+ : MainWindow(stubContext, JUCE_APPLICATION_NAME_STRING, true)
{
- using namespace StandaloneWindowConstants;
-
- juce::LookAndFeel::setDefaultLookAndFeel(&lookAndFeel);
-
- mainContentComponent.reset(new MainComponent(stubContext, JUCE_APPLICATION_NAME_STRING));
-
- setContentNonOwned(mainContentComponent.get(), true);
- centreWithSize(width, height);
- setResizable(true, true);
- setResizeLimits(minWidth, minHeight, (std::numeric_limits<int>::max)(), (std::numeric_limits<int>::max)());
- setVisible(true);
}
StandaloneWindow::~StandaloneWindow()
{
- juce::LookAndFeel::setDefaultLookAndFeel(nullptr);
- }
-
- int StandaloneWindow::getDesktopWindowStyleFlags() const
- {
- return juce::ComponentPeer::windowHasCloseButton | juce::ComponentPeer::windowHasTitleBar |
- juce::ComponentPeer::windowIsResizable | juce::ComponentPeer::windowHasMinimiseButton |
- juce::ComponentPeer::windowAppearsOnTaskbar | juce::ComponentPeer::windowHasMaximiseButton;
}
void StandaloneWindow::userTriedToCloseWindow()
{
- setVisible(false);
+ MainWindow::userTriedToCloseWindow();
+
juce::JUCEApplication::getInstance()->systemRequestedQuit();
}
} // namespace AK::WwiseTransfer
diff --git a/src/standalone/StandaloneWindow.h b/src/standalone/StandaloneWindow.h
@@ -1,22 +1,19 @@
#pragma once
#include "StubContext.h"
-#include "UI/MainComponent.h"
+#include "UI/MainWindow.h"
namespace AK::WwiseTransfer
{
- class StandaloneWindow : public juce::ResizableWindow
+ class StandaloneWindow : public MainWindow
{
public:
StandaloneWindow();
~StandaloneWindow() override;
- int getDesktopWindowStyleFlags() const override;
void userTriedToCloseWindow() override;
private:
- std::unique_ptr<MainComponent> mainContentComponent;
- CustomLookAndFeel lookAndFeel;
StubContext stubContext;
};
} // namespace AK::WwiseTransfer
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
@@ -13,6 +13,8 @@ target_sources(${PROJECT_NAME} PRIVATE ${TEST_SOURCES})
target_link_libraries(${PROJECT_NAME}
PRIVATE
WwiseTransfer_Shared
+ Reaper
+ ReaWwise_Static
PUBLIC
Catch2WithMain
trompeloeil::trompeloeil
diff --git a/src/test/ReaperContextTest.cpp b/src/test/ReaperContextTest.cpp
@@ -0,0 +1,455 @@
+#include "ReaperContext.h"
+
+#include "Helpers/StringHelper.h"
+
+#include <catch2/catch_all.hpp>
+#include <catch2/trompeloeil.hpp>
+
+namespace AK::ReaWwise::Test
+{
+#ifdef WIN32
+ juce::File projectDirectory = juce::File("C:\\MyProjectDirectory");
+ juce::File otherProjectDirectory = juce::File("C:\\OtherProjectDirectory");
+#else
+ juce::File projectDirectory = juce::File("/MyProjectDirectory");
+ juce::File otherProjectDirectory = juce::File("/OtherProjectDirectory");
+#endif
+
+ struct TestParams
+ {
+ TestParams(const juce::File& projectDirectory)
+ : projectDirectory(projectDirectory)
+ {
+ resolvedDummyRenderPattern = {
+ projectDirectory.getChildFile("-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("-002.wav").getFullPathName(),
+ };
+
+ renderTargets = {
+ projectDirectory.getChildFile("audio-file-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("audio-file-002.wav").getFullPathName(),
+ };
+
+ resolvedOriginalsSubfolder = {
+ projectDirectory.getChildFile("-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("-002.wav").getFullPathName(),
+ };
+
+ resolvedObjectPaths = {
+ "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio-file-001.wav",
+ "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio-file-002.wav",
+ };
+
+ renderStats << "FILE:" + projectDirectory.getChildFile("audio-file-001.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000;"
+ << "FILE:" + projectDirectory.getChildFile("audio-file-002.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000;";
+ }
+
+ juce::File projectDirectory;
+ juce::String outputFilename;
+ juce::String outputDirectory;
+
+ std::vector<juce::String> resolvedDummyRenderPattern;
+ std::vector<juce::String> renderTargets;
+ std::vector<juce::String> resolvedOriginalsSubfolder;
+ std::vector<juce::String> resolvedObjectPaths;
+
+ juce::String renderStats;
+ };
+
+ class MockReaperPlugin : public trompeloeil::mock_interface<IReaperPlugin>
+ {
+ public:
+ IMPLEMENT_CONST_MOCK0(getCallerVersion);
+ IMPLEMENT_CONST_MOCK2(registerFunction);
+ IMPLEMENT_CONST_MOCK0(isValid);
+
+ IMPLEMENT_MOCK0(getMainHwnd);
+ IMPLEMENT_MOCK0(addExtensionsMainMenu);
+ IMPLEMENT_MOCK3(enumProjects);
+ IMPLEMENT_MOCK4(getSetProjectInfo_String);
+ IMPLEMENT_MOCK5(resolveRenderPattern);
+ IMPLEMENT_MOCK2(main_OnCommand);
+ IMPLEMENT_MOCK5(getProjExtState);
+ IMPLEMENT_MOCK4(setProjExtState);
+ IMPLEMENT_MOCK1(markProjectDirty);
+ IMPLEMENT_MOCK1(getProjectStateChangeCount);
+ IMPLEMENT_MOCK4(getSetProjectInfo);
+ IMPLEMENT_MOCK2(reallocCmdRegisterBuf);
+ IMPLEMENT_MOCK1(reallocCmdClear);
+ IMPLEMENT_MOCK0(supportsReallocCommands);
+ };
+
+ struct GetItemsForPreviewExpectations
+ {
+ GetItemsForPreviewExpectations(MockReaperPlugin& plugin, const TestParams& params)
+ : plugin(plugin)
+ , reaproject(42)
+ , reaperProjectPath(params.projectDirectory.getChildFile("test.rpp").getFullPathName())
+ , outputDirectory(params.outputDirectory)
+ , dummyResolvedRenderPatternDblNullTerminated(WwiseTransfer::StringHelper::createDoubleNullTerminatedStringBuffer(params.resolvedDummyRenderPattern))
+ , resolvedOutputFilenameDblNullTerminated(WwiseTransfer::StringHelper::createDoubleNullTerminatedStringBuffer(params.renderTargets))
+ , resolvedOriginalsSubfolderDblNullTerminated(WwiseTransfer::StringHelper::createDoubleNullTerminatedStringBuffer(params.resolvedOriginalsSubfolder))
+ , resolvedObjectPathsDblNullTerminated(WwiseTransfer::StringHelper::createDoubleNullTerminatedStringBuffer(params.resolvedObjectPaths))
+ , renderFile("RENDER_FILE")
+ , renderPattern("RENDER_PATTERN")
+ , renderTargetsEx("RENDER_TARGETS_EX")
+ {
+ using trompeloeil::_; // wild card for matching any value
+
+ expectations[0] = NAMED_ALLOW_CALL(plugin, enumProjects(-1, _, _))
+ .SIDE_EFFECT(memset(_2, '\0', size_t(reaperProjectPath.length())))
+ .SIDE_EFFECT(memcpy(_2, reaperProjectPath.getCharPointer(), size_t(reaperProjectPath.length())))
+ .RETURN((ReaProject*)&reaproject);
+
+ expectations[1] = NAMED_ALLOW_CALL(plugin, supportsReallocCommands())
+ .RETURN(false);
+
+ expectations[2] = NAMED_REQUIRE_CALL(plugin, getSetProjectInfo_String(_, _, _, false))
+ .TIMES(1)
+ .WITH(juce::String(_2) == renderTargetsEx)
+ .SIDE_EFFECT(memset(_3, '\0', size_t(resolvedOutputFilenameDblNullTerminated.size())))
+ .SIDE_EFFECT(memcpy(_3, &resolvedOutputFilenameDblNullTerminated[0], size_t(resolvedOutputFilenameDblNullTerminated.size())))
+ .RETURN(true)
+ .IN_SEQUENCE(sequence);
+
+ expectations[3] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, nullptr, 0))
+ .TIMES(1)
+ .RETURN(dummyResolvedRenderPatternDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[4] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, _, _))
+ .TIMES(1)
+ .WITH(_4 != nullptr)
+ .SIDE_EFFECT(memset(_4, '\0', size_t(dummyResolvedRenderPatternDblNullTerminated.size())))
+ .SIDE_EFFECT(memcpy(_4, &dummyResolvedRenderPatternDblNullTerminated[0], size_t(dummyResolvedRenderPatternDblNullTerminated.size())))
+ .RETURN(dummyResolvedRenderPatternDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[5] = NAMED_REQUIRE_CALL(plugin, getSetProjectInfo_String(_, _, _, false))
+ .TIMES(1)
+ .WITH(juce::String(_2) == renderPattern)
+ .SIDE_EFFECT(memset(_3, '\0', size_t(renderPattern.length())))
+ .SIDE_EFFECT(memcpy(_3, renderPattern.getCharPointer(), size_t(renderPattern.length())))
+ .RETURN(true)
+ .IN_SEQUENCE(sequence);
+
+ expectations[6] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, nullptr, 0))
+ .TIMES(1)
+ .RETURN(resolvedOutputFilenameDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[7] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, _, _))
+ .TIMES(1)
+ .WITH(_4 != nullptr)
+ .SIDE_EFFECT(memset(_4, '\0', size_t(resolvedOutputFilenameDblNullTerminated.size())))
+ .SIDE_EFFECT(memcpy(_4, &resolvedOutputFilenameDblNullTerminated[0], size_t(resolvedOutputFilenameDblNullTerminated.size())))
+ .RETURN(resolvedOutputFilenameDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[8] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, nullptr, 0))
+ .TIMES(1)
+ .RETURN(resolvedOriginalsSubfolderDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[9] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, _, _, _, _))
+ .TIMES(1)
+ .WITH(_4 != nullptr)
+ .SIDE_EFFECT(memset(_4, '\0', size_t(resolvedOriginalsSubfolderDblNullTerminated.size())))
+ .SIDE_EFFECT(memcpy(_4, &resolvedOriginalsSubfolderDblNullTerminated[0], size_t(resolvedOriginalsSubfolderDblNullTerminated.size())))
+ .RETURN(resolvedOriginalsSubfolderDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[10] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, nullptr, _, nullptr, 0))
+ .TIMES(1)
+ .RETURN(resolvedObjectPathsDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+
+ expectations[11] = NAMED_REQUIRE_CALL(plugin, resolveRenderPattern(_, nullptr, _, _, _))
+ .TIMES(1)
+ .WITH(_4 != nullptr)
+ .SIDE_EFFECT(memset(_4, '\0', size_t(resolvedObjectPathsDblNullTerminated.size())))
+ .SIDE_EFFECT(memcpy(_4, &resolvedObjectPathsDblNullTerminated[0], size_t(resolvedObjectPathsDblNullTerminated.size())))
+ .RETURN(resolvedObjectPathsDblNullTerminated.size())
+ .IN_SEQUENCE(sequence);
+ }
+
+ protected:
+ MockReaperPlugin& plugin;
+ trompeloeil::sequence sequence;
+ int reaproject;
+ juce::String reaperProjectPath;
+
+ private:
+ juce::String outputDirectory;
+ std::vector<char> dummyResolvedRenderPatternDblNullTerminated;
+ std::vector<char> resolvedOutputFilenameDblNullTerminated;
+ std::vector<char> resolvedOriginalsSubfolderDblNullTerminated;
+ std::vector<char> resolvedObjectPathsDblNullTerminated;
+ juce::String renderFile;
+ juce::String renderPattern;
+ juce::String renderTargetsEx;
+
+ std::array<std::unique_ptr<trompeloeil::expectation>, 12> expectations;
+ };
+
+ struct GetItemsForImportExpectations : private GetItemsForPreviewExpectations
+ {
+ GetItemsForImportExpectations(MockReaperPlugin& plugin, const TestParams& params)
+ : GetItemsForPreviewExpectations(plugin, params)
+ , renderStatsKey("RENDER_STATS")
+ , renderStats(params.renderStats)
+ {
+ using trompeloeil::_; // wild card for matching any value
+
+ expectations[0] = NAMED_REQUIRE_CALL(plugin, getSetProjectInfo_String(_, _, _, false))
+ .TIMES(1)
+ .WITH(juce::String(_2) == renderStatsKey)
+ .SIDE_EFFECT(memset(_3, '\0', size_t(renderStats.length())))
+ .SIDE_EFFECT(memcpy(_3, renderStats.getCharPointer(), size_t(renderStats.length())))
+ .RETURN(true)
+ .IN_SEQUENCE(sequence);
+ }
+
+ private:
+ juce::String renderStatsKey;
+ juce::String renderStats;
+ std::array<std::unique_ptr<trompeloeil::expectation>, 1> expectations;
+ };
+
+ std::vector<WwiseTransfer::Import::PreviewItem> getItemsForPreview(const TestParams& params)
+ {
+ WwiseTransfer::Import::Options importOptions{"", "", ""};
+
+ MockReaperPlugin plugin;
+ ReaperContext reaperContext(plugin);
+
+ GetItemsForPreviewExpectations expectations(plugin, params);
+
+ return reaperContext.getItemsForPreview(importOptions);
+ }
+
+ std::vector<WwiseTransfer::Import::Item> getItemsForImport(const TestParams& params)
+ {
+ WwiseTransfer::Import::Options importOptions{"", "", ""};
+
+ MockReaperPlugin plugin;
+ ReaperContext reaperContext(plugin);
+
+ GetItemsForImportExpectations expectations(plugin, params);
+
+ return reaperContext.getItemsForImport(importOptions);
+ }
+
+ SCENARIO("ReaperContext getItemsForImport")
+ {
+ TestParams params(projectDirectory);
+
+ GIVEN("Default test params")
+ {
+ WHEN("Render stats contains a semi-colon at the end")
+ {
+ auto importItems = getItemsForImport(params);
+
+ THEN("The resulting render file paths must be parsed correctly")
+ {
+ REQUIRE(importItems.size() == 2);
+ REQUIRE(importItems[0].renderFilePath == projectDirectory.getChildFile("audio-file-001.wav").getFullPathName());
+ REQUIRE(importItems[1].renderFilePath == projectDirectory.getChildFile("audio-file-002.wav").getFullPathName());
+ }
+ }
+
+ AND_WHEN("Render stats does not contain semi-colon at the end")
+ {
+ params.renderStats.clear();
+ params.renderStats << "FILE:" + projectDirectory.getChildFile("audio-file-001.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000;"
+ << "FILE:" + projectDirectory.getChildFile("audio-file-002.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000";
+
+ auto importItems = getItemsForImport(params);
+
+ THEN("The resulting render file paths must be parsed correctly")
+ {
+ REQUIRE(importItems.size() == 2);
+ REQUIRE(importItems[0].renderFilePath == projectDirectory.getChildFile("audio-file-001.wav").getFullPathName());
+ REQUIRE(importItems[1].renderFilePath == projectDirectory.getChildFile("audio-file-002.wav").getFullPathName());
+ }
+ }
+
+ AND_WHEN("Render stats contains some unexpected text before the first \"FILE:\"")
+ {
+ params.renderStats.clear();
+ params.renderStats << "Unex:pect;edTextFILE:" + projectDirectory.getChildFile("audio-file-001.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000;"
+ << "FILE:" + projectDirectory.getChildFile("audio-file-002.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000";
+
+ auto importItems = getItemsForImport(params);
+
+ THEN("The resulting render file paths must be parsed correctly")
+ {
+ REQUIRE(importItems.size() == 2);
+ REQUIRE(importItems[0].renderFilePath == projectDirectory.getChildFile("audio-file-001.wav").getFullPathName());
+ REQUIRE(importItems[1].renderFilePath == projectDirectory.getChildFile("audio-file-002.wav").getFullPathName());
+ }
+ }
+
+ AND_WHEN("Render stats does not contain \"FILE:\"")
+ {
+ params.renderStats.clear();
+ params.renderStats << projectDirectory.getChildFile("audio-file-001.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000;"
+ << projectDirectory.getChildFile("audio-file-002.wav").getFullPathName() + ";PEAK:-0000.000000;LRA:-0000.000000;LUFSMMAX:-0000.000000;LUFSSMAX:-0000.000000;LUFSI:-0000.000000";
+
+ auto importItems = getItemsForImport(params);
+
+ THEN("No import items are returned")
+ {
+ REQUIRE(importItems.size() == 0);
+ }
+ }
+
+ AND_WHEN("Render stats is empty")
+ {
+ params.renderStats.clear();
+
+ auto importItems = getItemsForImport(params);
+
+ THEN("No import items are returned")
+ {
+ REQUIRE(importItems.size() == 0);
+ }
+ }
+ }
+ }
+
+ SCENARIO("ReaperContext getItemsForPreview")
+ {
+ TestParams params(projectDirectory);
+
+ const juce::String upOneDirectorySymbol = ".." + juce::File::getSeparatorString();
+
+ GIVEN("Default test params")
+ {
+ THEN("The resulting audio file will match the render target returned by REAPER")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].audioFilePath == params.renderTargets[0]);
+ REQUIRE(previewItems[1].audioFilePath == params.renderTargets[1]);
+ }
+
+ AND_WHEN("The configured originals subfolder is empty")
+ {
+ THEN("The originals subfolder parameter for the import item should be empty")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].originalsSubFolder.isEmpty());
+ REQUIRE(previewItems[1].originalsSubFolder.isEmpty());
+ }
+ }
+
+ AND_WHEN("The configured originals subfolder is not empty")
+ {
+ juce::String expectedOriginalsSubfolder = "OriginalsSubfolder";
+
+ params.resolvedOriginalsSubfolder = {
+ projectDirectory.getChildFile(expectedOriginalsSubfolder).getChildFile("-001.wav").getFullPathName(),
+ projectDirectory.getChildFile(expectedOriginalsSubfolder).getChildFile("-002.wav").getFullPathName(),
+ };
+
+ THEN("The originals subfolder parameter for the import item should match the configured originals subfolder")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].originalsSubFolder == expectedOriginalsSubfolder);
+ REQUIRE(previewItems[1].originalsSubFolder == expectedOriginalsSubfolder);
+ }
+
+ AND_WHEN("The audio file path contains subdirectories")
+ {
+ params.renderTargets = {
+ projectDirectory.getChildFile("AudioFolder").getChildFile("audio-file-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("AudioFolder").getChildFile("audio-file-002.wav").getFullPathName(),
+ };
+
+ THEN("The originals subfolder for the preview item should be a combination of the configured originals subfolder and the audio file path's folder relative to the render directory")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].originalsSubFolder == juce::String("OriginalsSubfolder") + juce::File::getSeparatorChar() + "AudioFolder");
+ REQUIRE(previewItems[1].originalsSubFolder == juce::String("OriginalsSubfolder") + juce::File::getSeparatorChar() + "AudioFolder");
+ }
+ }
+
+ AND_WHEN("The audio file path contains " + upOneDirectorySymbol)
+ {
+ params.renderTargets = {
+ projectDirectory.getChildFile(upOneDirectorySymbol + "audio-file-001.wav").getFullPathName(),
+ projectDirectory.getChildFile(upOneDirectorySymbol + "audio-file-002.wav").getFullPathName(),
+ };
+
+ THEN("The originals subfolder for the preview item should be empty since it would be cancelled out due to the " + upOneDirectorySymbol)
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].originalsSubFolder.isEmpty());
+ REQUIRE(previewItems[1].originalsSubFolder.isEmpty());
+ }
+ }
+ }
+
+ AND_WHEN("The audio file path contains a semi-colon")
+ {
+ params.renderTargets = {
+ projectDirectory.getChildFile("audio;-file-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("audio;-file-002.wav").getFullPathName(),
+ };
+
+ THEN("The resulting audio file path should match the expected value")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].audioFilePath == projectDirectory.getChildFile("audio;-file-001.wav").getFullPathName());
+ REQUIRE(previewItems[1].audioFilePath == projectDirectory.getChildFile("audio;-file-002.wav").getFullPathName());
+ }
+ }
+
+ AND_WHEN("The audio file path contains an extra period")
+ {
+ params.renderTargets = {
+ projectDirectory.getChildFile("audio.file-001.wav").getFullPathName(),
+ projectDirectory.getChildFile("audio.file-002.wav").getFullPathName(),
+ };
+
+ THEN("The resulting audio file path should match the expected value")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].audioFilePath == projectDirectory.getChildFile("audio.file-001.wav").getFullPathName());
+ REQUIRE(previewItems[1].audioFilePath == projectDirectory.getChildFile("audio.file-002.wav").getFullPathName());
+ }
+ }
+
+ AND_WHEN("The object path contains an extra period")
+ {
+ params.resolvedObjectPaths = {
+ "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio.file-001.wav",
+ "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio.file-002.wav",
+ };
+
+ THEN("The resulting object path should be the resolved object path without the file extension")
+ {
+ auto previewItems = getItemsForPreview(params);
+
+ REQUIRE(previewItems.size() == 2);
+ REQUIRE(previewItems[0].path == "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio.file-001");
+ REQUIRE(previewItems[1].path == "\\Actor-Mixer Hierarchy\\Default Work Unit\\<Random Container>Footsteps\\<SoundSFX>audio.file-002");
+ }
+ }
+ }
+ }
+} // namespace AK::ReaWwise::Test
diff --git a/src/test/WwiseHelperTests.h b/src/test/WwiseHelperTests.h
@@ -8,8 +8,8 @@
namespace AK::WwiseTransfer::Test
{
- std::unordered_map <Wwise::ObjectType, juce::String> objectTypeStringMap{
- {Wwise::ObjectType::ActorMixer, "Actor Mixer"},
+ std::unordered_map<Wwise::ObjectType, juce::String> objectTypeStringMap{
+ {Wwise::ObjectType::ActorMixer, "Actor-Mixer"},
{Wwise::ObjectType::AudioFileSource, "Audio File Source"},
{Wwise::ObjectType::BlendContainer, "Blend Container"},
{Wwise::ObjectType::PhysicalFolder, "Physical Folder"},
@@ -21,10 +21,9 @@ namespace AK::WwiseTransfer::Test
{Wwise::ObjectType::VirtualFolder, "Virtual Folder"},
{Wwise::ObjectType::WorkUnit, "Work Unit"},
{Wwise::ObjectType::Sound, "Sound"},
- {Wwise::ObjectType::Unknown, "Unknown"}
- };
+ {Wwise::ObjectType::Unknown, "Unknown"}};
- std::vector <Wwise::ObjectType> objectTypes{
+ std::vector<Wwise::ObjectType> objectTypes{
Wwise::ObjectType::ActorMixer,
Wwise::ObjectType::AudioFileSource,
Wwise::ObjectType::BlendContainer,
@@ -37,6 +36,5 @@ namespace AK::WwiseTransfer::Test
Wwise::ObjectType::VirtualFolder,
Wwise::ObjectType::WorkUnit,
Wwise::ObjectType::Sound,
- Wwise::ObjectType::Unknown
- };
-} // namespace AK::WwiseTransfer:Test
+ Wwise::ObjectType::Unknown};
+} // namespace AK::WwiseTransfer::Test