fixed issue

This commit is contained in:
Ji Yoon Rhee
2024-02-10 17:06:19 -05:00
parent b045551102
commit 2c8481bd61
647 changed files with 97393 additions and 6970 deletions

View File

@ -0,0 +1,146 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "AssetTypeActions_FMODEvent.h"
#include "AssetTypeActions_Base.h"
#include "FMODEventEditor.h"
#include "FMODEvent.h"
#include "FMODUtils.h"
#include "FMODStudioModule.h"
#include "FMODStudioEditorModule.h"
#include "UnrealEd/Public/Editor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "EditorStyle/Public/EditorStyleSet.h"
#define LOCTEXT_NAMESPACE "AssetTypeActions"
FAssetTypeActions_FMODEvent::FAssetTypeActions_FMODEvent()
: CurrentPreviewEventInstance(nullptr)
{
BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FAssetTypeActions_FMODEvent::HandleBeginPIE);
IFMODStudioEditorModule::Get().BanksReloadedEvent().AddRaw(this, &FAssetTypeActions_FMODEvent::HandleBanksReloaded);
}
FAssetTypeActions_FMODEvent::~FAssetTypeActions_FMODEvent()
{
if (GIsRunning)
{
FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle);
IFMODStudioEditorModule::Get().BanksReloadedEvent().RemoveAll(this);
IFMODStudioModule::Get().StopAuditioningInstance();
}
}
UClass *FAssetTypeActions_FMODEvent::GetSupportedClass() const
{
return UFMODEvent::StaticClass();
}
void FAssetTypeActions_FMODEvent::GetActions(const TArray<UObject *> &InObjects, FMenuBuilder &MenuBuilder)
{
auto Events = GetTypedWeakObjectPtrs<UFMODEvent>(InObjects);
MenuBuilder.AddMenuEntry(LOCTEXT("FMODEvent_Play", "Play"), LOCTEXT("FMODEvent_PlayTooltip", "Plays the selected FMOD event."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "MediaAsset.AssetActions.Play"),
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::ExecutePlay, Events),
FCanExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::CanExecutePlayCommand, Events)));
MenuBuilder.AddMenuEntry(LOCTEXT("FMODEvent_Stop", "Stop"), LOCTEXT("FMODEvent_StopTooltip", "Stops the currently playing FMOD event."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "MediaAsset.AssetActions.Stop"),
FUIAction(FExecuteAction::CreateSP(this, &FAssetTypeActions_FMODEvent::ExecuteStop, Events), FCanExecuteAction()));
}
void FAssetTypeActions_FMODEvent::OpenAssetEditor(const TArray<UObject *> &InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor)
{
EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
{
auto Event = Cast<UFMODEvent>(*ObjIt);
if (IsValid(Event))
{
TSharedRef<FFMODEventEditor> NewFMODEventEditor(new FFMODEventEditor());
NewFMODEventEditor->InitFMODEventEditor(Mode, EditWithinLevelEditor, Event);
}
}
}
bool FAssetTypeActions_FMODEvent::CanExecutePlayCommand(TArray<TWeakObjectPtr<UFMODEvent>> Objects) const
{
return Objects.Num() == 1;
}
bool FAssetTypeActions_FMODEvent::AssetsActivatedOverride(const TArray<UObject *> &InObjects, EAssetTypeActivationMethod::Type ActivationType)
{
if (ActivationType == EAssetTypeActivationMethod::Previewed)
{
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
{
UFMODEvent *Event = Cast<UFMODEvent>(*ObjIt);
if (IsValid(Event))
{
// Only play the first valid event
PlayEvent(Event);
break;
}
}
return true;
}
return false;
}
void FAssetTypeActions_FMODEvent::ExecuteEdit(TArray<TWeakObjectPtr<UFMODEvent>> Objects)
{
for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
{
auto Object = (*ObjIt).Get();
if (IsValid(Object))
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Object);
}
}
}
void FAssetTypeActions_FMODEvent::ExecutePlay(TArray<TWeakObjectPtr<UFMODEvent>> Objects)
{
for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
{
UFMODEvent *Event = (*ObjIt).Get();
if (IsValid(Event))
{
// Only play the first valid event
PlayEvent(Event);
break;
}
}
}
void FAssetTypeActions_FMODEvent::ExecuteStop(TArray<TWeakObjectPtr<UFMODEvent>> Objects)
{
IFMODStudioModule::Get().StopAuditioningInstance();
}
void FAssetTypeActions_FMODEvent::PlayEvent(UFMODEvent *Event)
{
if (IsValid(Event))
{
CurrentPreviewEventInstance = IFMODStudioModule::Get().CreateAuditioningInstance(Event);
if (CurrentPreviewEventInstance != nullptr)
{
CurrentPreviewEventInstance->start();
}
}
}
void FAssetTypeActions_FMODEvent::HandleBeginPIE(bool bSimulating)
{
// Studio module will handle its own auditioning, just clear the handle
CurrentPreviewEventInstance = nullptr;
}
void FAssetTypeActions_FMODEvent::HandleBanksReloaded()
{
// Studio module will handle its own auditioning, just clear the handle
CurrentPreviewEventInstance = nullptr;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,56 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "AssetTypeActions_Base.h"
namespace FMOD
{
namespace Studio
{
class EventInstance;
}
}
class UFMODEvent;
class FAssetTypeActions_FMODEvent : public FAssetTypeActions_Base
{
public:
FAssetTypeActions_FMODEvent();
~FAssetTypeActions_FMODEvent();
// IAssetTypeActions Implementation
virtual FText GetName() const override { return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_FMODEvent", "FMOD Event"); }
virtual FColor GetTypeColor() const override { return FColor(0, 175, 255); }
virtual UClass *GetSupportedClass() const override;
virtual bool HasActions(const TArray<UObject *> &InObjects) const override { return true; }
virtual void GetActions(const TArray<UObject *> &InObjects, FMenuBuilder &MenuBuilder) override;
virtual bool AssetsActivatedOverride(const TArray<UObject *> &InObjects, EAssetTypeActivationMethod::Type ActivationType) override;
virtual void OpenAssetEditor(
const TArray<UObject *> &InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
virtual bool CanFilter() override { return false; }
virtual uint32 GetCategories() override { return EAssetTypeCategories::Sounds; }
private:
/** Returns true if only one event is selected to play */
bool CanExecutePlayCommand(TArray<TWeakObjectPtr<UFMODEvent>> Objects) const;
/** Handler for when Edit is selected */
void ExecuteEdit(TArray<TWeakObjectPtr<UFMODEvent>> Objects);
/** Handler for when Play is selected */
void ExecutePlay(TArray<TWeakObjectPtr<UFMODEvent>> Objects);
/** Handler for when Stop is selected */
void ExecuteStop(TArray<TWeakObjectPtr<UFMODEvent>> Objects);
/** Plays the event */
void PlayEvent(UFMODEvent *Event);
void HandleBeginPIE(bool bSimulating);
void HandleBanksReloaded();
FMOD::Studio::EventInstance *CurrentPreviewEventInstance;
FDelegateHandle BeginPIEDelegateHandle;
};

View File

@ -0,0 +1,67 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODAmbientSoundActorFactory.h"
#include "FMODStudioEditorPrivatePCH.h"
#include "FMODAmbientSound.h"
#include "FMODEvent.h"
#include "AssetRegistry/Public/AssetData.h"
#include "Editor/EditorEngine.h"
UFMODAmbientSoundActorFactory::UFMODAmbientSoundActorFactory(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
{
DisplayName = NSLOCTEXT("FMOD", "FMODAmbientSoundDisplayName", "FMOD Ambient Sound");
NewActorClass = AFMODAmbientSound::StaticClass();
}
bool UFMODAmbientSoundActorFactory::CanCreateActorFrom(const FAssetData &AssetData, FText &OutErrorMsg)
{
//We allow creating AAmbientSounds without an existing sound asset
if (UActorFactory::CanCreateActorFrom(AssetData, OutErrorMsg))
{
return true;
}
if (AssetData.IsValid() && !AssetData.GetClass()->IsChildOf(UFMODEvent::StaticClass()))
{
OutErrorMsg = NSLOCTEXT("FMOD", "CanCreateActorFrom_NoFMODEventAsset", "A valid FMOD Event asset must be specified.");
return false;
}
return true;
}
void UFMODAmbientSoundActorFactory::PostSpawnActor(UObject *Asset, AActor *NewActor)
{
UFMODEvent *Event = Cast<UFMODEvent>(Asset);
if (Event != NULL)
{
AFMODAmbientSound *NewSound = CastChecked<AFMODAmbientSound>(NewActor);
FActorLabelUtilities::SetActorLabelUnique(NewSound, Event->GetName());
NewSound->AudioComponent->Event = Event;
}
}
UObject *UFMODAmbientSoundActorFactory::GetAssetFromActorInstance(AActor *Instance)
{
check(Instance->IsA(NewActorClass));
AFMODAmbientSound *SoundActor = CastChecked<AFMODAmbientSound>(Instance);
check(SoundActor->AudioComponent);
return SoundActor->AudioComponent->Event;
}
void UFMODAmbientSoundActorFactory::PostCreateBlueprint(UObject *Asset, AActor *CDO)
{
if (Asset != NULL && CDO != NULL)
{
UFMODEvent *Event = Cast<UFMODEvent>(Asset);
if (Event != NULL)
{
AFMODAmbientSound *NewSound = CastChecked<AFMODAmbientSound>(CDO);
NewSound->AudioComponent->Event = Event;
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "ComponentAssetBroker.h"
#include "FMODEvent.h"
//////////////////////////////////////////////////////////////////////////
// FFMODAssetBroker
class FFMODAssetBroker : public IComponentAssetBroker
{
public:
UClass *GetSupportedAssetClass() override { return UFMODEvent::StaticClass(); }
virtual bool AssignAssetToComponent(UActorComponent *InComponent, UObject *InAsset) override
{
if (UFMODAudioComponent *AudioComp = Cast<UFMODAudioComponent>(InComponent))
{
UFMODEvent *Event = Cast<UFMODEvent>(InAsset);
if ((Event != NULL) || (InAsset == NULL))
{
AudioComp->Event = Event;
return true;
}
}
return false;
}
virtual UObject *GetAssetFromComponent(UActorComponent *InComponent) override
{
if (UFMODAudioComponent *AudioComp = Cast<UFMODAudioComponent>(InComponent))
{
return AudioComp->Event;
}
return NULL;
}
};

View File

@ -0,0 +1,655 @@
#include "FMODAssetBuilder.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "FMODAssetLookup.h"
#include "FMODAssetTable.h"
#include "FMODBank.h"
#include "FMODBankLookup.h"
#include "FMODBus.h"
#include "FMODEvent.h"
#include "FMODSettings.h"
#include "FMODSnapshot.h"
#include "FMODSnapshotReverb.h"
#include "FMODPort.h"
#include "FMODUtils.h"
#include "FMODVCA.h"
#include "FileHelpers.h"
#include "ObjectTools.h"
#include "SourceControlHelpers.h"
#include "HAL/FileManager.h"
#include "Misc/MessageDialog.h"
#include "fmod_studio.hpp"
#define LOCTEXT_NAMESPACE "FMODAssetBuilder"
FFMODAssetBuilder::~FFMODAssetBuilder()
{
if (StudioSystem)
{
StudioSystem->release();
}
}
void FFMODAssetBuilder::Create()
{
verifyfmod(FMOD::Studio::System::create(&StudioSystem));
FMOD::System *lowLevelSystem = nullptr;
verifyfmod(StudioSystem->getCoreSystem(&lowLevelSystem));
verifyfmod(lowLevelSystem->setOutput(FMOD_OUTPUTTYPE_NOSOUND_NRT));
verifyfmod(StudioSystem->initialize(1, FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS | FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE, FMOD_INIT_MIX_FROM_UPDATE,
nullptr));
}
void FFMODAssetBuilder::ProcessBanks()
{
TArray<UObject*> AssetsToSave;
TArray<UObject*> AssetsToDelete;
const UFMODSettings& Settings = *GetDefault<UFMODSettings>();
FString PackagePath = Settings.GetFullContentPath() / FFMODAssetTable::PrivateDataPath();
BuildBankLookup(FFMODAssetTable::BankLookupName(), PackagePath, Settings, AssetsToSave);
BuildAssets(Settings, FFMODAssetTable::AssetLookupName(), PackagePath, AssetsToSave, AssetsToDelete);
SaveAssets(AssetsToSave);
DeleteAssets(AssetsToDelete);
}
FString FFMODAssetBuilder::GetMasterStringsBankPath()
{
return BankLookup ? BankLookup->MasterStringsBankPath : FString();
}
void FFMODAssetBuilder::BuildAssets(const UFMODSettings& InSettings, const FString &AssetLookupName, const FString &AssetLookupPath,
TArray<UObject*>& AssetsToSave, TArray<UObject*>& AssetsToDelete)
{
if (!BankLookup->MasterStringsBankPath.IsEmpty())
{
FString StringPath = InSettings.GetFullBankPath() / BankLookup->MasterStringsBankPath;
UE_LOG(LogFMOD, Log, TEXT("Loading strings bank: %s"), *StringPath);
FMOD::Studio::Bank *StudioStringBank;
FMOD_RESULT StringResult = StudioSystem->loadBankFile(TCHAR_TO_UTF8(*StringPath), FMOD_STUDIO_LOAD_BANK_NORMAL, &StudioStringBank);
if (StringResult == FMOD_OK)
{
TArray<char> RawBuffer;
RawBuffer.SetNum(256); // Initial capacity
int Count = 0;
verifyfmod(StudioStringBank->getStringCount(&Count));
// Enumerate all of the names in the strings bank and gather the information required to create the UE4 assets for each object
TArray<AssetCreateInfo> AssetCreateInfos;
AssetCreateInfos.Reserve(Count);
for (int StringIdx = 0; StringIdx < Count; ++StringIdx)
{
FMOD::Studio::ID Guid = { 0 };
while (true)
{
int ActualSize = 0;
FMOD_RESULT Result = StudioStringBank->getStringInfo(StringIdx, &Guid, RawBuffer.GetData(), RawBuffer.Num(), &ActualSize);
if (Result == FMOD_ERR_TRUNCATED)
{
RawBuffer.SetNum(ActualSize);
}
else
{
verifyfmod(Result);
break;
}
}
FString AssetName(UTF8_TO_TCHAR(RawBuffer.GetData()));
FGuid AssetGuid = FMODUtils::ConvertGuid(Guid);
if (!AssetName.IsEmpty())
{
AssetCreateInfo CreateInfo = {};
if (MakeAssetCreateInfo(AssetGuid, AssetName, &CreateInfo))
{
AssetCreateInfos.Add(CreateInfo);
}
}
}
verifyfmod(StudioStringBank->unload());
verifyfmod(StudioSystem->update());
// Load or create asset lookup
FString AssetLookupPackageName = AssetLookupPath + AssetLookupName;
UPackage *AssetLookupPackage = CreatePackage(*AssetLookupPackageName);
AssetLookupPackage->FullyLoad();
bool bAssetLookupCreated = false;
bool bAssetLookupModified = false;
UDataTable *AssetLookup = FindObject<UDataTable>(AssetLookupPackage, *AssetLookupName, true);
if (!AssetLookup)
{
AssetLookup = NewObject<UDataTable>(AssetLookupPackage, *AssetLookupName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
AssetLookup->RowStruct = FFMODAssetLookupRow::StaticStruct();
bAssetLookupCreated = true;
}
// Create a list of existing assets in the lookup - we'll use this to delete stale assets
TMap<FName, FFMODAssetLookupRow> StaleAssets{};
AssetLookup->ForeachRow<FFMODAssetLookupRow>(FString(), [&StaleAssets](const FName& Key, const FFMODAssetLookupRow& Value) {
StaleAssets.Add(Key, Value);
});
for (const AssetCreateInfo &CreateInfo : AssetCreateInfos)
{
UFMODAsset *Asset = CreateAsset(CreateInfo, AssetsToSave);
if (Asset)
{
UPackage *AssetPackage = Asset->GetPackage();
FString AssetPackageName = AssetPackage->GetPathName();
FString AssetName = Asset->GetPathName(AssetPackage);
FName LookupRowName = FName(*CreateInfo.StudioPath);
FFMODAssetLookupRow* LookupRow = AssetLookup->FindRow<FFMODAssetLookupRow>(LookupRowName, FString(), false);
if (LookupRow)
{
if (LookupRow->PackageName != AssetPackageName || LookupRow->AssetName != AssetName)
{
LookupRow->PackageName = AssetPackageName;
LookupRow->AssetName = AssetName;
bAssetLookupModified = true;
}
}
else
{
FFMODAssetLookupRow NewRow{};
NewRow.PackageName = AssetPackageName;
NewRow.AssetName = AssetName;
AssetLookup->AddRow(LookupRowName, NewRow);
bAssetLookupModified = true;
}
StaleAssets.Remove(LookupRowName);
}
}
// Delete stale assets
if (StaleAssets.Num() > 0)
{
for (auto& Entry : StaleAssets)
{
UPackage *Package = CreatePackage(*Entry.Value.PackageName);
Package->FullyLoad();
UFMODAsset *Asset = Package ? FindObject<UFMODAsset>(Package, *Entry.Value.AssetName) : nullptr;
if (Asset)
{
UE_LOG(LogFMOD, Log, TEXT("Deleting stale asset %s/%s."), *Entry.Value.PackageName, *Entry.Value.AssetName);
AssetsToDelete.Add(Asset);
}
AssetLookup->RemoveRow(Entry.Key);
}
bAssetLookupModified = true;
}
if (bAssetLookupCreated || bAssetLookupModified)
{
AssetsToSave.Add(AssetLookup);
}
}
else
{
UE_LOG(LogFMOD, Warning, TEXT("Failed to load strings bank: %s"), *StringPath);
}
}
}
const FString BankExtensions[] = { TEXT(".assets"), TEXT(".streams"), TEXT(".bank")};
void FFMODAssetBuilder::BuildBankLookup(const FString &AssetName, const FString &PackagePath, const UFMODSettings &InSettings,
TArray<UObject*>& AssetsToSave)
{
FString PackageName = PackagePath + AssetName;
UPackage *Package = CreatePackage(*PackageName);
Package->FullyLoad();
bool bCreated = false;
bool bModified = false;
BankLookup = FindObject<UFMODBankLookup>(Package, *AssetName, true);
if (!BankLookup)
{
BankLookup = NewObject<UFMODBankLookup>(Package, *AssetName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
BankLookup->DataTable = NewObject<UDataTable>(BankLookup, "DataTable", RF_NoFlags);
BankLookup->DataTable->RowStruct = FFMODLocalizedBankTable::StaticStruct();
bCreated = true;
}
// Get a list of all bank GUIDs already in the lookup - this will be used to remove stale GUIDs after processing
// the current banks on disk.
TArray<FName> StaleBanks(BankLookup->DataTable->GetRowNames());
// Process all banks on disk
TArray<FString> BankPaths;
FString SearchDir = InSettings.GetFullBankPath();
IFileManager::Get().FindFilesRecursive(BankPaths, *SearchDir, TEXT("*.bank"), true, false, false);
if (BankPaths.Num() <= 0)
{
return;
}
TMap<FString, FString> BankGuids;
for (FString BankPath : BankPaths)
{
FMOD::Studio::Bank* Bank;
FMOD_RESULT result = StudioSystem->loadBankFile(TCHAR_TO_UTF8(*BankPath), FMOD_STUDIO_LOAD_BANK_NORMAL, &Bank);
FMOD_GUID BankID;
if (result == FMOD_OK)
{
result = Bank->getID(&BankID);
Bank->unload();
}
if (result == FMOD_OK)
{
FString GUID = FMODUtils::ConvertGuid(BankID).ToString(EGuidFormats::DigitsWithHyphensInBraces);
FString* otherBankPath = BankGuids.Find(GUID);
if (otherBankPath != nullptr)
{
bool foundLocale = false;
for (const FFMODProjectLocale& Locale : InSettings.Locales)
{
if (Locale.bDefault && BankPath.EndsWith(FString("_") + Locale.LocaleCode + FString(".bank")))
{
foundLocale = true;
break;
}
}
if (!foundLocale)
{
UE_LOG(LogFMOD, Warning, TEXT("Ignoring bank %s as another bank with the same GUID is already being used.\n"
"Bank %s does not match any locales in the FMOD Studio plugin settings."), *BankPath, *BankPath);
continue;
}
}
BankGuids.Add(GUID, BankPath);
}
else
{
UE_LOG(LogFMOD, Error, TEXT("Failed to add bank %s to lookup."), *BankPath);
}
}
for (TPair<FString,FString> GUIDPath : BankGuids)
{
FName OuterRowName(*GUIDPath.Key);
FFMODLocalizedBankTable* Row = BankLookup->DataTable->FindRow<FFMODLocalizedBankTable>(OuterRowName, nullptr, false);
if (Row)
{
StaleBanks.RemoveSingle(OuterRowName);
}
else
{
FFMODLocalizedBankTable NewRow{};
NewRow.Banks = NewObject<UDataTable>(BankLookup->DataTable, *GUIDPath.Key, RF_NoFlags);
NewRow.Banks->RowStruct = FFMODLocalizedBankRow::StaticStruct();
BankLookup->DataTable->AddRow(OuterRowName, NewRow);
Row = BankLookup->DataTable->FindRow<FFMODLocalizedBankTable>(OuterRowName, nullptr, false);
bModified = true;
}
FString CurFilename = FPaths::GetCleanFilename(GUIDPath.Value);
FString FilenamePart = CurFilename;
for (const FString& extension : BankExtensions)
{
FilenamePart.ReplaceInline(*extension, TEXT(""));
}
FString InnerRowName("<NON-LOCALIZED>");
for (const FFMODProjectLocale& Locale : InSettings.Locales)
{
if (FilenamePart.EndsWith(FString("_") + Locale.LocaleCode))
{
InnerRowName = Locale.LocaleCode;
break;
}
}
FFMODLocalizedBankRow* InnerRow = Row->Banks->FindRow<FFMODLocalizedBankRow>(FName(*InnerRowName), nullptr, false);
FString RelativeBankPath = GUIDPath.Value.RightChop(InSettings.GetFullBankPath().Len() + 1);
if (InnerRow)
{
if (InnerRow->Path != RelativeBankPath)
{
InnerRow->Path = RelativeBankPath;
bModified = true;
}
}
else
{
FFMODLocalizedBankRow NewRow{};
NewRow.Path = RelativeBankPath;
Row->Banks->AddRow(FName(*InnerRowName), NewRow);
bModified = true;
}
if (CurFilename == InSettings.GetMasterBankFilename() && BankLookup->MasterBankPath != RelativeBankPath)
{
BankLookup->MasterBankPath = RelativeBankPath;
bModified = true;
}
else if (CurFilename == InSettings.GetMasterStringsBankFilename() && BankLookup->MasterStringsBankPath != RelativeBankPath)
{
BankLookup->MasterStringsBankPath = RelativeBankPath;
bModified = true;
}
else if (CurFilename == InSettings.GetMasterAssetsBankFilename() && BankLookup->MasterAssetsBankPath != RelativeBankPath)
{
BankLookup->MasterAssetsBankPath = RelativeBankPath;
bModified = true;
}
}
StudioSystem->flushCommands();
// Remove stale banks from lookup
if (StaleBanks.Num() > 0)
{
for (const auto& RowName : StaleBanks)
{
BankLookup->DataTable->RemoveRow(RowName);
}
bModified = true;
}
if (bCreated)
{
FAssetRegistryModule::AssetCreated(BankLookup);
}
if (bCreated || bModified)
{
AssetsToSave.Add(BankLookup);
}
}
FString FFMODAssetBuilder::GetAssetClassName(UClass* AssetClass)
{
FString ClassName("");
if (AssetClass == UFMODEvent::StaticClass())
{
ClassName = TEXT("Events");
}
else if (AssetClass == UFMODSnapshot::StaticClass())
{
ClassName = TEXT("Snapshots");
}
else if (AssetClass == UFMODBank::StaticClass())
{
ClassName = TEXT("Banks");
}
else if (AssetClass == UFMODBus::StaticClass())
{
ClassName = TEXT("Buses");
}
else if (AssetClass == UFMODVCA::StaticClass())
{
ClassName = TEXT("VCAs");
}
else if (AssetClass == UFMODSnapshotReverb::StaticClass())
{
ClassName = TEXT("Reverbs");
}
else if (AssetClass == UFMODPort::StaticClass())
{
ClassName = TEXT("Ports");
}
return ClassName;
}
bool FFMODAssetBuilder::MakeAssetCreateInfo(const FGuid &AssetGuid, const FString &StudioPath, AssetCreateInfo *CreateInfo)
{
CreateInfo->StudioPath = StudioPath;
CreateInfo->Guid = AssetGuid;
FString AssetType;
FString AssetPath;
StudioPath.Split(TEXT(":"), &AssetType, &AssetPath, ESearchCase::CaseSensitive, ESearchDir::FromStart);
if (AssetType.Equals(TEXT("event")))
{
CreateInfo->Class = UFMODEvent::StaticClass();
}
else if (AssetType.Equals(TEXT("snapshot")))
{
CreateInfo->Class = UFMODSnapshot::StaticClass();
}
else if (AssetType.Equals(TEXT("bank")))
{
CreateInfo->Class = UFMODBank::StaticClass();
}
else if (AssetType.Equals(TEXT("bus")))
{
CreateInfo->Class = UFMODBus::StaticClass();
}
else if (AssetType.Equals(TEXT("vca")))
{
CreateInfo->Class = UFMODVCA::StaticClass();
}
else if (AssetType.Equals(TEXT("port")))
{
CreateInfo->Class = UFMODPort::StaticClass();
}
else if (AssetType.Equals(TEXT("parameter")))
{
return false;
}
else
{
UE_LOG(LogFMOD, Warning, TEXT("Unknown asset type: %s"), *AssetType);
CreateInfo->Class = UFMODAsset::StaticClass();
}
AssetPath.Split(TEXT("/"), &(CreateInfo->Path), &(CreateInfo->AssetName), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
if (CreateInfo->AssetName.IsEmpty() || CreateInfo->AssetName.Contains(TEXT(".strings")))
{
return false;
}
return true;
}
UFMODAsset *FFMODAssetBuilder::CreateAsset(const AssetCreateInfo& CreateInfo, TArray<UObject*>& AssetsToSave)
{
FString SanitizedAssetName;
FText OutReason;
if (FName::IsValidXName(CreateInfo.AssetName, INVALID_OBJECTNAME_CHARACTERS, &OutReason))
{
SanitizedAssetName = CreateInfo.AssetName;
}
else
{
SanitizedAssetName = ObjectTools::SanitizeObjectName(CreateInfo.AssetName);
UE_LOG(LogFMOD, Log, TEXT("'%s' cannot be used as a UE4 asset name. %s. Using '%s' instead."), *CreateInfo.AssetName,
*OutReason.ToString(), *SanitizedAssetName);
}
const UFMODSettings &Settings = *GetDefault<UFMODSettings>();
FString Folder = Settings.GetFullContentPath() / GetAssetClassName(CreateInfo.Class) + CreateInfo.Path;
FString PackagePath = FString::Printf(TEXT("%s/%s"), *Folder, *SanitizedAssetName);
FString SanitizedPackagePath;
if (FName::IsValidXName(PackagePath, INVALID_LONGPACKAGE_CHARACTERS, &OutReason))
{
SanitizedPackagePath = PackagePath;
}
else
{
SanitizedPackagePath = ObjectTools::SanitizeInvalidChars(PackagePath, INVALID_OBJECTPATH_CHARACTERS);
UE_LOG(LogFMOD, Log, TEXT("'%s' cannot be used as a UE4 asset path. %s. Using '%s' instead."), *PackagePath, *OutReason.ToString(),
*SanitizedPackagePath);
}
UPackage *Package = CreatePackage(*SanitizedPackagePath);
Package->FullyLoad();
UFMODAsset *Asset = FindObject<UFMODAsset>(Package, *SanitizedAssetName);
bool bCreated = false;
bool bModified = false;
if (Asset && Asset->GetClass() == CreateInfo.Class)
{
if (Asset->AssetGuid != CreateInfo.Guid)
{
UE_LOG(LogFMOD, Log, TEXT("Updating asset: %s"), *SanitizedPackagePath);
Asset->AssetGuid = CreateInfo.Guid;
bModified = true;
}
}
else
{
UE_LOG(LogFMOD, Log, TEXT("Adding asset: %s"), *SanitizedPackagePath);
Asset = NewObject<UFMODAsset>(Package, CreateInfo.Class, FName(*SanitizedAssetName), RF_Standalone | RF_Public | RF_MarkAsRootSet);
Asset->AssetGuid = CreateInfo.Guid;
bCreated = true;
}
if (bCreated)
{
FAssetRegistryModule::AssetCreated(Asset);
}
if (bCreated || bModified)
{
AssetsToSave.Add(Asset);
}
if (!IsValid(Asset))
{
UE_LOG(LogFMOD, Error, TEXT("Failed to construct asset: %s"), *SanitizedPackagePath);
}
if (CreateInfo.Class == UFMODSnapshot::StaticClass())
{
FString OldPrefix = Settings.ContentBrowserPrefix + GetAssetClassName(Asset->GetClass());
FString NewPrefix = Settings.ContentBrowserPrefix + GetAssetClassName(UFMODSnapshotReverb::StaticClass());
UObject *Outer = Asset->GetOuter() ? Asset->GetOuter() : Asset;
FString ReverbPackagePath = Outer->GetPathName().Replace(*OldPrefix, *NewPrefix);
UPackage *ReverbPackage = CreatePackage(*ReverbPackagePath);
ReverbPackage->FullyLoad();
UFMODSnapshotReverb *AssetReverb = FindObject<UFMODSnapshotReverb>(ReverbPackage, *SanitizedAssetName, true);
bCreated = false;
bModified = false;
if (AssetReverb)
{
if (AssetReverb->AssetGuid != CreateInfo.Guid)
{
UE_LOG(LogFMOD, Log, TEXT("Updating snapshot reverb asset: %s"), *ReverbPackagePath);
AssetReverb->AssetGuid = CreateInfo.Guid;
bModified = true;
}
}
else
{
UE_LOG(LogFMOD, Log, TEXT("Constructing snapshot reverb asset: %s"), *ReverbPackagePath);
AssetReverb = NewObject<UFMODSnapshotReverb>(ReverbPackage, UFMODSnapshotReverb::StaticClass(), FName(*SanitizedAssetName),
RF_Standalone | RF_Public | RF_MarkAsRootSet);
AssetReverb->AssetGuid = CreateInfo.Guid;
bCreated = true;
}
if (bCreated)
{
FAssetRegistryModule::AssetCreated(AssetReverb);
}
if (bCreated || bModified)
{
AssetsToSave.Add(AssetReverb);
}
if (!IsValid(AssetReverb))
{
UE_LOG(LogFMOD, Error, TEXT("Failed to construct snapshot reverb asset: %s"), *ReverbPackagePath);
}
}
return Asset;
}
void FFMODAssetBuilder::SaveAssets(TArray<UObject*>& AssetsToSave)
{
if (AssetsToSave.Num() == 0)
{
return;
}
TArray<UPackage *> PackagesToSave;
for (auto& Asset : AssetsToSave)
{
UPackage* Package = Asset->GetPackage();
if (Package)
{
Package->MarkPackageDirty();
PackagesToSave.Add(Package);
}
}
UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true);
}
void FFMODAssetBuilder::DeleteAssets(TArray<UObject*>& AssetsToDelete)
{
if (AssetsToDelete.Num() == 0)
{
return;
}
TArray<UObject*> ObjectsToDelete;
for (auto& Asset : AssetsToDelete)
{
ObjectsToDelete.Add(Asset);
if (Asset->GetClass() == UFMODSnapshot::StaticClass())
{
// Also delete the reverb asset
const UFMODSettings &Settings = *GetDefault<UFMODSettings>();
FString OldPrefix = Settings.ContentBrowserPrefix + GetAssetClassName(Asset->GetClass());
FString NewPrefix = Settings.ContentBrowserPrefix + GetAssetClassName(UFMODSnapshotReverb::StaticClass());
FString ReverbName = Asset->GetPathName().Replace(*OldPrefix, *NewPrefix);
UObject *Reverb = StaticFindObject(UFMODSnapshotReverb::StaticClass(), nullptr, *ReverbName);
if (Reverb)
{
ObjectsToDelete.Add(Reverb);
}
}
}
// Use ObjectTools to delete assets - ObjectTools::DeleteObjects handles confirmation, source control, and making read only files writables
ObjectTools::DeleteObjects(ObjectsToDelete, !IsRunningCommandlet());
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,115 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODAudioComponentDetails.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "FMODAmbientSound.h"
#include "FMODStudioModule.h"
#include "FMODEvent.h"
#include "fmod_studio.hpp"
#include "UnrealEd/Public/Editor.h"
#include "Widgets/Input/SButton.h"
#include "PropertyEditor/Public/DetailLayoutBuilder.h"
#include "PropertyEditor/Public/DetailCategoryBuilder.h"
#define LOCTEXT_NAMESPACE "FMODStudio"
TSharedRef<IDetailCustomization> FFMODAudioComponentDetails::MakeInstance()
{
return MakeShareable(new FFMODAudioComponentDetails);
}
void FFMODAudioComponentDetails::CustomizeDetails(IDetailLayoutBuilder &DetailBuilder)
{
const TArray<TWeakObjectPtr<UObject>> &SelectedObjects = DetailBuilder.GetSelectedObjects();
for (int32 ObjectIndex = 0; !AudioComponent.IsValid() && ObjectIndex < SelectedObjects.Num(); ++ObjectIndex)
{
const TWeakObjectPtr<UObject> &CurrentObject = SelectedObjects[ObjectIndex];
if (CurrentObject.Get()->GetClass()->IsChildOf(UFMODAudioComponent::StaticClass()))
{
AudioComponent = Cast<UFMODAudioComponent>(CurrentObject.Get());
}
else
{
AudioComponent = Cast<AActor>(CurrentObject.Get())->FindComponentByClass<UFMODAudioComponent>();
}
}
DetailBuilder.EditCategory(TEXT("FMODAudio"))
.AddCustomRow(
FText::GetEmpty())[SNew(SVerticalBox) +
SVerticalBox::Slot()
.Padding(0, 2.0f, 0, 0)
.FillHeight(1.0f)
.VAlign(
VAlign_Center)[SNew(SHorizontalBox) +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)[SNew(SButton)
.VAlign(VAlign_Center)
.OnClicked(this, &FFMODAudioComponentDetails::OnEditSoundClicked)
.Text(LOCTEXT("View Details", "Details"))] +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)[SNew(SButton)
.VAlign(VAlign_Center)
.OnClicked(this, &FFMODAudioComponentDetails::OnPlaySoundClicked)
.Text(LOCTEXT("Play FMOD Event", "Play"))] +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)[SNew(SButton)
.VAlign(VAlign_Center)
.OnClicked(this, &FFMODAudioComponentDetails::OnStopSoundClicked)
.Text(LOCTEXT("Stop FMOD Event", "Stop"))]]];
}
FReply FFMODAudioComponentDetails::OnEditSoundClicked()
{
if (AudioComponent.IsValid())
{
UFMODEvent *Event = AudioComponent.Get()->Event;
if (Event)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Event);
}
}
return FReply::Handled();
}
FReply FFMODAudioComponentDetails::OnPlaySoundClicked()
{
if (AudioComponent.IsValid())
{
UFMODEvent *Event = AudioComponent.Get()->Event;
if (IsValid(Event))
{
FMOD::Studio::EventInstance *Instance = IFMODStudioModule::Get().CreateAuditioningInstance(Event);
if (Instance)
{
for (auto param : AudioComponent->ParameterCache)
{
Instance->setParameterByName(TCHAR_TO_UTF8(*param.Key.ToString()), param.Value);
}
Instance->start();
}
}
}
return FReply::Handled();
}
FReply FFMODAudioComponentDetails::OnStopSoundClicked()
{
IFMODStudioModule::Get().StopAuditioningInstance();
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,24 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
//#include "PropertyEditing.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditor/Public/IDetailCustomization.h"
class FFMODAudioComponentDetails : public IDetailCustomization
{
public:
/** Makes a new instance of this detail layout class for a specific detail view requesting it */
static TSharedRef<IDetailCustomization> MakeInstance();
private:
/** IDetailCustomization interface */
virtual void CustomizeDetails(IDetailLayoutBuilder &DetailBuilder) override;
FReply OnEditSoundClicked();
FReply OnPlaySoundClicked();
FReply OnStopSoundClicked();
TWeakObjectPtr<class UFMODAudioComponent> AudioComponent;
};

View File

@ -0,0 +1,54 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODAudioComponentVisualizer.h"
#include "FMODAudioComponent.h"
#include "FMODUtils.h"
#include "FMODEvent.h"
#include "fmod_studio.hpp"
#include "Engine/Public/SceneView.h"
#include "Engine/Public/SceneManagement.h"
void FFMODAudioComponentVisualizer::DrawVisualization(const UActorComponent *Component, const FSceneView *View, FPrimitiveDrawInterface *PDI)
{
if (View->Family->EngineShowFlags.AudioRadius)
{
const UFMODAudioComponent *AudioComp = Cast<const UFMODAudioComponent>(Component);
if (IsValid(AudioComp) && AudioComp->Event)
{
FMOD::Studio::EventDescription *EventDesc =
IFMODStudioModule::Get().GetEventDescription(AudioComp->Event, EFMODSystemContext::Auditioning);
if (EventDesc != nullptr)
{
bool bIs3D = false;
EventDesc->is3D(&bIs3D);
if (bIs3D)
{
const FColor AudioOuterRadiusColor(255, 153, 0);
const FColor AudioInnerRadiusColor(216, 130, 0);
const FTransform &Transform = AudioComp->GetComponentTransform();
float MinDistance = 0.0f;
float MaxDistance = 0.0f;
if (AudioComp->AttenuationDetails.bOverrideAttenuation)
{
MinDistance = AudioComp->AttenuationDetails.MinimumDistance;
MaxDistance = AudioComp->AttenuationDetails.MaximumDistance;
}
else
{
EventDesc->getMinMaxDistance(&MinDistance, &MaxDistance);
}
MinDistance = FMODUtils::DistanceToUEScale(MinDistance);
MaxDistance = FMODUtils::DistanceToUEScale(MaxDistance);
DrawWireSphereAutoSides(PDI, Transform.GetTranslation(), AudioOuterRadiusColor, MinDistance, SDPG_World);
if (MaxDistance != MinDistance)
{
DrawWireSphereAutoSides(PDI, Transform.GetTranslation(), AudioInnerRadiusColor, MaxDistance, SDPG_World);
}
}
}
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "ComponentVisualizer.h"
class FFMODAudioComponentVisualizer : public FComponentVisualizer
{
public:
// Begin FComponentVisualizer interface
virtual void DrawVisualization(const UActorComponent *Component, const FSceneView *View, FPrimitiveDrawInterface *PDI) override;
// End FComponentVisualizer interface
};

View File

@ -0,0 +1,96 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODBankUpdateNotifier.h"
#include "FMODSettings.h"
#include "HAL/FileManager.h"
#include "FMODStudioEditorPrivatePCH.h"
FFMODBankUpdateNotifier::FFMODBankUpdateNotifier()
: bUpdateEnabled(true)
, NextRefreshTime(FDateTime::MinValue())
, FileTime(FDateTime::MinValue())
, Countdown(0.0f)
{
}
void FFMODBankUpdateNotifier::SetFilePath(const FString &InPath)
{
FilePath = InPath;
NextRefreshTime = FDateTime::MinValue();
FileTime = MostRecentFileTime();
}
void FFMODBankUpdateNotifier::Update(float DeltaTime)
{
if (bUpdateEnabled)
{
FDateTime CurTime = FDateTime::UtcNow();
if (CurTime >= NextRefreshTime)
{
Refresh();
NextRefreshTime = CurTime + FTimespan(0, 0, 1);
}
if (Countdown > 0.0f)
{
Countdown -= DeltaTime;
if (Countdown <= 0.0f)
{
BanksUpdatedEvent.Broadcast();
}
}
}
}
void FFMODBankUpdateNotifier::EnableUpdate(bool bEnable)
{
bUpdateEnabled = bEnable;
if (bEnable)
{
// Refreshing right after update is enabled is not desirable
NextRefreshTime = FDateTime::UtcNow() + FTimespan(0, 0, 1);
// Cancel any pending countdown
Countdown = 0.0f;
}
}
void FFMODBankUpdateNotifier::Refresh()
{
if (!FilePath.IsEmpty())
{
FDateTime NewFileTime = MostRecentFileTime();
if (NewFileTime != FileTime)
{
const UFMODSettings &Settings = *GetDefault<UFMODSettings>();
Countdown = (float)Settings.ReloadBanksDelay;
FileTime = NewFileTime;
}
}
}
FDateTime FFMODBankUpdateNotifier::MostRecentFileTime()
{
// Get the most recent modified timestamp of all the bank files in the directory we are watching.
FDateTime MostRecent = FDateTime::MinValue();
TArray<FString> BankPaths;
IFileManager::Get().FindFilesRecursive(BankPaths, *FilePath, TEXT("*.bank"), true, false, false);
for (const auto& Path : BankPaths)
{
FDateTime ModifiedTime = IFileManager::Get().GetTimeStamp(*Path);
if (ModifiedTime > MostRecent)
{
MostRecent = ModifiedTime;
}
}
return MostRecent;
}

View File

@ -0,0 +1,30 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "Containers/UnrealString.h"
#include "Misc/DateTime.h"
#include "Delegates/Delegate.h"
class FFMODBankUpdateNotifier
{
public:
FFMODBankUpdateNotifier();
void SetFilePath(const FString &InPath);
void Update(float DeltaTime);
void EnableUpdate(bool bEnable);
FSimpleMulticastDelegate BanksUpdatedEvent;
private:
void Refresh();
FDateTime MostRecentFileTime();
bool bUpdateEnabled;
FString FilePath;
FDateTime NextRefreshTime;
FDateTime FileTime;
float Countdown;
};

View File

@ -0,0 +1,189 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODEventEditor.h"
#include "FMODEvent.h"
#include "FMODStudioEditorModule.h"
#include "FMODStudioModule.h"
#include "FMODUtils.h"
#include "SFMODEventEditorPanel.h"
#include "Widgets/Docking/SDockTab.h"
#include "fmod_studio.hpp"
#include "UnrealEd/Public/Editor.h"
#define LOCTEXT_NAMESPACE "FMODEventEditor"
DEFINE_LOG_CATEGORY_STATIC(LogFMODEventEditor, Log, All);
const FName FFMODEventEditor::EventEditorTabId(TEXT("FFMODEventEditor_EventView"));
const FName FFMODEventEditor::FMODEventEditorAppIdentifier(TEXT("FMODEventEditorApp"));
void FFMODEventEditor::RegisterTabSpawners(const TSharedRef<class FTabManager> &NewTabManager)
{
WorkspaceMenuCategory = NewTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_FMODEventEditor", "FMOD Event Editor"));
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(NewTabManager);
NewTabManager->RegisterTabSpawner(EventEditorTabId, FOnSpawnTab::CreateSP(this, &FFMODEventEditor::SpawnTab_EventEditor))
.SetDisplayName(LOCTEXT("EventTab", "FMOD Event"))
.SetGroup(WorkspaceMenuCategoryRef);
}
void FFMODEventEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager> &NewTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(NewTabManager);
NewTabManager->UnregisterTabSpawner(EventEditorTabId);
}
FFMODEventEditor::FFMODEventEditor()
: CurrentPreviewEventInstance(nullptr)
, EditedEvent(nullptr)
{
IFMODStudioEditorModule::Get().BanksReloadedEvent().AddRaw(this, &FFMODEventEditor::HandleBanksReloaded);
BeginPIEDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FFMODEventEditor::HandleBeginPIE);
}
FFMODEventEditor::~FFMODEventEditor()
{
IFMODStudioEditorModule::Get().BanksReloadedEvent().RemoveAll(this);
FEditorDelegates::BeginPIE.Remove(BeginPIEDelegateHandle);
CurrentPreviewEventInstance = nullptr;
}
UFMODEvent *FFMODEventEditor::GetEditedEvent() const
{
return EditedEvent;
}
FMOD::Studio::EventDescription *FFMODEventEditor::GetEventDescription() const
{
return IFMODStudioModule::Get().GetEventDescription(EditedEvent, EFMODSystemContext::Auditioning);
}
void FFMODEventEditor::PlayEvent()
{
CurrentPreviewEventInstance = IFMODStudioModule::Get().CreateAuditioningInstance(EditedEvent);
if (CurrentPreviewEventInstance != nullptr)
{
TArray<float> values;
TArray<FMOD_STUDIO_PARAMETER_ID> ids;
ParameterValues.GenerateKeyArray(ids);
ParameterValues.GenerateValueArray(values);
CurrentPreviewEventInstance->setParametersByIDs(ids.GetData(), values.GetData(), ParameterValues.Num());
CurrentPreviewEventInstance->start();
}
}
void FFMODEventEditor::PauseEvent()
{
if (CurrentPreviewEventInstance != nullptr)
{
bool bIsPaused = false;
CurrentPreviewEventInstance->getPaused(&bIsPaused);
CurrentPreviewEventInstance->setPaused(!bIsPaused);
}
}
void FFMODEventEditor::StopEvent()
{
IFMODStudioModule::Get().StopAuditioningInstance();
}
void FFMODEventEditor::SetParameterValue(FMOD_STUDIO_PARAMETER_ID ParameterId, float Value)
{
ParameterValues[ParameterId] = Value;
if (CurrentPreviewEventInstance != nullptr)
{
CurrentPreviewEventInstance->setParameterByID(ParameterId, Value);
}
}
void FFMODEventEditor::AddParameter(FMOD_STUDIO_PARAMETER_ID ParameterId, float Value)
{
ParameterValues.Add(ParameterId, Value);
}
float FFMODEventEditor::GetParameterValue(FMOD_STUDIO_PARAMETER_ID Id)
{
return ParameterValues[Id];
}
void FFMODEventEditor::InitFMODEventEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost> &InitToolkitHost, UFMODEvent *Event)
{
if (IsValid(Event))
{
EditedEvent = Event;
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout =
FTabManager::NewLayout("Standalone_FMODEventEditor_Layout")
->AddArea(FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical)
->Split(FTabManager::NewStack()->AddTab(EventEditorTabId, ETabState::OpenedTab)->SetHideTabWell(true)));
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = false;
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, FFMODEventEditor::FMODEventEditorAppIdentifier, StandaloneDefaultLayout,
bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Event);
}
}
FName FFMODEventEditor::GetToolkitFName() const
{
return FName("FMODEventEditor");
}
FText FFMODEventEditor::GetBaseToolkitName() const
{
return LOCTEXT("ToolkitName", "FMOD Event Editor");
}
FString FFMODEventEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "FMOD Event ").ToString();
}
FLinearColor FFMODEventEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.0f, 0.0f, 0.5f, 0.5f);
}
void FFMODEventEditor::CreateInternalWidgets()
{
FMODEventEditorPanel = SNew(SFMODEventEditorPanel).FMODEventEditor(SharedThis(this));
}
TSharedRef<SDockTab> FFMODEventEditor::SpawnTab_EventEditor(const FSpawnTabArgs &Args)
{
check(Args.GetTabId().TabType == EventEditorTabId);
CreateInternalWidgets();
return SAssignNew(OwnerTab, SDockTab)
.Label(LOCTEXT("EventEditorTitle", "FMOD Event"))
.TabColorScale(GetTabColorScale())[FMODEventEditorPanel.ToSharedRef()];
}
void FFMODEventEditor::HandleBanksReloaded()
{
CurrentPreviewEventInstance = nullptr;
CreateInternalWidgets();
if (OwnerTab.IsValid())
{
OwnerTab->SetContent(FMODEventEditorPanel.ToSharedRef());
}
}
void FFMODEventEditor::HandleBeginPIE(bool bSimulating)
{
CurrentPreviewEventInstance = nullptr;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,88 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "Toolkits/AssetEditorToolkit.h"
#include "fmod_studio_common.h"
namespace FMOD
{
namespace Studio
{
class EventDescription;
class EventInstance;
}
}
static bool operator==(const FMOD_STUDIO_PARAMETER_ID &a, const FMOD_STUDIO_PARAMETER_ID &b)
{
return (a.data1 == b.data1 && a.data2 == b.data2);
}
FORCEINLINE uint32 GetTypeHash(const FMOD_STUDIO_PARAMETER_ID& id)
{
return FCrc::MemCrc_DEPRECATED(&id, sizeof(FMOD_STUDIO_PARAMETER_ID));
}
class FFMODEventEditor : public FAssetEditorToolkit
{
public:
virtual void RegisterTabSpawners(const TSharedRef<class FTabManager> &NewTabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<class FTabManager> &NewTabManager) override;
/**
* Edits the specified event
*
* @param Mode Asset editing mode for this editor (standalone or world-centric)
* @param InitToolkitHost When Mode is WorldCentric, this is the level editor instance to spawn this editor within
* @param Event The event to edit
*/
void InitFMODEventEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost> &InitToolkitHost, class UFMODEvent *Event);
/** Constructor */
FFMODEventEditor();
/** Destructor */
virtual ~FFMODEventEditor();
UFMODEvent *GetEditedEvent() const;
FMOD::Studio::EventDescription *GetEventDescription() const;
void PlayEvent();
void PauseEvent();
void StopEvent();
float GetParameterValue(FMOD_STUDIO_PARAMETER_ID Id);
void SetParameterValue(FMOD_STUDIO_PARAMETER_ID ParameterId, float Value);
void AddParameter(FMOD_STUDIO_PARAMETER_ID ParameterId, float Value);
/** IToolkit interface */
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
private:
TMap<FMOD_STUDIO_PARAMETER_ID, float> ParameterValues;
FMOD::Studio::EventInstance *CurrentPreviewEventInstance;
void HandlePreBanksReloaded();
void HandleBanksReloaded();
void HandleBeginPIE(bool bSimulating);
/** Creates all internal widgets for the tabs to point at */
void CreateInternalWidgets();
/** Spawns the tab with the FMOD event inside */
TSharedRef<SDockTab> SpawnTab_EventEditor(const FSpawnTabArgs &Args);
TSharedPtr<class SFMODEventEditorPanel> FMODEventEditorPanel;
TSharedPtr<SDockTab> OwnerTab;
/** The tab id for the event editor tab */
static const FName EventEditorTabId;
/** FMOD event editor app identifier string */
static const FName FMODEventEditorAppIdentifier;
class UFMODEvent *EditedEvent;
FDelegateHandle BeginPIEDelegateHandle;
};

View File

@ -0,0 +1,70 @@
// Copyright (c), Firelight Technologies Pty, Ltd.
#include "FMODGenerateAssetsCommandlet.h"
#include "FMODSettings.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Editor.h"
#include "Editor/UnrealEd/Public/FileHelpers.h"
#include "HAL/PlatformFileManager.h"
#include "../Classes/FMODAssetBuilder.h"
DEFINE_LOG_CATEGORY_STATIC(LogFMODCommandlet, Log, All);
static constexpr auto RebuildSwitch = TEXT("rebuild");
UFMODGenerateAssetsCommandlet::UFMODGenerateAssetsCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
int32 UFMODGenerateAssetsCommandlet::Main(const FString& CommandLine)
{
int32 returnCode = 0;
#if WITH_EDITOR
FAssetRegistryModule& assetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName);
IAssetRegistry& AssetRegistry = assetRegistryModule.Get();
const UFMODSettings& Settings = *GetDefault<UFMODSettings>();
TArray<FString> Tokens, Switches;
TMap<FString, FString> Params;
ParseCommandLine(*CommandLine, Tokens, Switches, Params);
// Rebuild switch
if (Switches.Contains(RebuildSwitch))
{
FString FolderToDelete;
IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile();
for (FString folder : Settings.GeneratedFolders)
{
FolderToDelete = FPaths::ProjectContentDir() + Settings.ContentBrowserPrefix + folder;
bool removed = FileManager.DeleteDirectoryRecursively(*FolderToDelete);
if (!removed)
{
UE_LOG(LogFMODCommandlet, Warning, TEXT("Unable to delete '%s'."), *FolderToDelete);
}
}
}
// Ensure AssetRegistry is up to date
TArray<FString> InPaths;
InPaths.Add(Settings.GetFullContentPath());
AssetRegistry.ScanPathsSynchronous(InPaths);
while (AssetRegistry.IsLoadingAssets())
{
AssetRegistry.Tick(1.0f);
}
FFMODAssetBuilder assetBuilder;
if (!IsEngineExitRequested())
{
assetBuilder.Create();
assetBuilder.ProcessBanks();
}
#endif
return returnCode;
}

View File

@ -0,0 +1,237 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2023.
#include "FMODSettingsCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "FMODSettings.h"
#include "IDetailPropertyRow.h"
#include "Settings/ProjectPackagingSettings.h"
#include "Styling/SlateColor.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SWidget.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#define LOCTEXT_NAMESPACE "FMODSettings"
class SSettingsMessage : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SSettingsMessage)
{}
// Called when the Setup button is clicked
SLATE_EVENT(FSimpleDelegate, OnSetupClicked)
SLATE_END_ARGS()
public:
void Construct(const FArguments& InArgs)
{
TSharedRef<SWidget> SettingsOkayWidget = MakeRow(
"SettingsEditor.GoodIcon",
LOCTEXT("SettingsOkayText", "FMOD Settings are valid, run the Validate FMOD command to perform additional checking."),
FText()
);
TSharedRef<SWidget> NoContentDirWidget = MakeRow(
"SettingsEditor.WarningIcon",
LOCTEXT("NoContentDirText", "Bank Output Directory directory has not been set."),
FText()
);
TSharedRef<SWidget> PackagingSettingsBadWidget = MakeRow(
"SettingsEditor.WarningIcon",
LOCTEXT("PackagingSettingsBadText",
"The packaging settings for copying the FMOD bank files to staging are not correct. It is recommended that:\n"
" - The bank output directory for the Desktop platform (or the forced platform if set) is added to the \"Additional Non-Asset Directories To Copy\" list.\n"
" - That no other directory containing FMOD banks or assets is added to either the \"Additional Non-Asset Directories To Copy\" list "
"or the \"Additional Non-Asset Directories to Package\" list.\n"
" - The Generated Assets are added to the \"Additional Asset Directories to Cook\" list."
),
LOCTEXT("FixPackagingSettings", "Fix")
);
ChildSlot
[
SNew(SBorder)
.BorderBackgroundColor(this, &SSettingsMessage::GetBorderColor)
.BorderImage(FAppStyle::GetBrush("ToolPanel.LightGroupBorder"))
.Padding(8.0f)
[
SNew(SWidgetSwitcher)
.WidgetIndex(this, &SSettingsMessage::GetSetupStateAsInt)
+ SWidgetSwitcher::Slot()
[
SettingsOkayWidget
]
+ SWidgetSwitcher::Slot()
[
NoContentDirWidget
]
+ SWidgetSwitcher::Slot()
[
PackagingSettingsBadWidget
]
]
];
UpdateState();
}
void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
UpdateState();
}
private:
FSlateColor GetBorderColor() const
{
if (SettingsState == UFMODSettings::Okay)
{
return FLinearColor::Green;
}
else
{
return FLinearColor(0.8f, 0, 0);
}
}
TSharedRef<SWidget> MakeRow(FName IconName, FText Message, FText ButtonMessage)
{
TSharedRef<SHorizontalBox> Result = SNew(SHorizontalBox)
// Status icon
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)[SNew(SImage).Image(FAppStyle::GetBrush(IconName))]
// Notice
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(16.0f, 0.0f)
.VAlign(VAlign_Center)[
SNew(STextBlock)
.ColorAndOpacity(FLinearColor::White)
.ShadowColorAndOpacity(FLinearColor::Black)
.ShadowOffset(FVector2D::UnitVector)
.AutoWrapText(true)
.Text(Message)
];
if (!ButtonMessage.IsEmpty())
{
Result->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SButton)
.OnClicked(this, &SSettingsMessage::OnButtonPressed)
.Text(ButtonMessage)
];
}
return Result;
}
FReply OnButtonPressed()
{
const UFMODSettings& Settings = *GetDefault<UFMODSettings>();
UProjectPackagingSettings* PackagingSettings = Cast<UProjectPackagingSettings>(UProjectPackagingSettings::StaticClass()->GetDefaultObject());
if (SettingsState == UFMODSettings::PackagingSettingsBad)
{
// Remove any bad entries
for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysStageAsNonUFS.Num();)
{
if (PackagingSettings->DirectoriesToAlwaysStageAsNonUFS[i].Path.StartsWith(Settings.BankOutputDirectory.Path))
{
PackagingSettings->DirectoriesToAlwaysStageAsNonUFS.RemoveAt(i);
}
else
{
++i;
}
}
for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysStageAsUFS.Num();)
{
if (PackagingSettings->DirectoriesToAlwaysStageAsUFS[i].Path.StartsWith(Settings.BankOutputDirectory.Path))
{
PackagingSettings->DirectoriesToAlwaysStageAsUFS.RemoveAt(i);
}
else
{
++i;
}
}
for (int i = 0; i < PackagingSettings->DirectoriesToAlwaysCook.Num();)
{
if (PackagingSettings->DirectoriesToAlwaysCook[i].Path.StartsWith(Settings.GetFullContentPath()))
{
PackagingSettings->DirectoriesToAlwaysCook.RemoveAt(i);
}
else
{
++i;
}
}
// Add correct entry
FDirectoryPath BankPath;
BankPath.Path = Settings.GetDesktopBankPath();
PackagingSettings->DirectoriesToAlwaysStageAsNonUFS.Add(BankPath);
FDirectoryPath generatedFolder;
for (FString folder : Settings.GeneratedFolders)
{
generatedFolder.Path = Settings.GetFullContentPath() / folder;
PackagingSettings->DirectoriesToAlwaysCook.Add(generatedFolder);
}
PackagingSettings->TryUpdateDefaultConfigFile();
}
UpdateState();
return FReply::Handled();
}
int32 GetSetupStateAsInt() const
{
return (int32)SettingsState;
}
void UpdateState()
{
const UFMODSettings& Settings = *GetDefault<UFMODSettings>();
SettingsState = Settings.Check();
}
private:
UFMODSettings::EProblem SettingsState;
};
TSharedRef<IDetailCustomization> FFMODSettingsCustomization::MakeInstance()
{
return MakeShareable(new FFMODSettingsCustomization);
}
FFMODSettingsCustomization::FFMODSettingsCustomization()
{
}
void FFMODSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder &DetailLayout)
{
IDetailCategoryBuilder &PackagingCategory = DetailLayout.EditCategory(TEXT("Notice"), FText::GetEmpty(), ECategoryPriority::Important);
TSharedRef<SSettingsMessage> PlatformSetupMessage = SNew(SSettingsMessage);
PackagingCategory.AddCustomRow(LOCTEXT("Warning", "Warning"), false).WholeRowWidget[PlatformSetupMessage];
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,25 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2023.
#pragma once
#include "CoreMinimal.h"
#include "Misc/Attribute.h"
#include "Input/Reply.h"
#include "IDetailCustomization.h"
#include "PropertyHandle.h"
class IDetailLayoutBuilder;
class FFMODSettingsCustomization : public IDetailCustomization
{
public:
// Makes a new instance of this detail layout class for a specific detail view requesting it
static TSharedRef<IDetailCustomization> MakeInstance();
// IDetailCustomization interface
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override;
// End of IDetailCustomization interface
private:
FFMODSettingsCustomization();
};

View File

@ -0,0 +1,19 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "Runtime/Engine/Classes/Components/SceneComponent.h"
#include "Runtime/Engine/Classes/Camera/CameraComponent.h"
#include "Runtime/Engine/Classes/Curves/IntegralCurve.h"
#include "Runtime/Engine/Classes/Curves/NameCurve.h"
#include "Runtime/Engine/Classes/Curves/RichCurve.h"
#include "Runtime/MovieScene/Public/MovieScene.h"
#include "Runtime/MovieScene/Public/KeyParams.h"
#include "Editor/Sequencer/Public/ISectionLayoutBuilder.h"
#include "Editor/Sequencer/Public/ISequencerSection.h"
#include "Editor/Sequencer/Public/MovieSceneTrackEditor.h"
#include "Runtime/MovieScene/Public/MovieSceneCommonHelpers.h"
#include "Runtime/MovieScene/Public/MovieSceneSection.h"
#include "Editor/UnrealEd/Public/ScopedTransaction.h"
DECLARE_LOG_CATEGORY_EXTERN(LogFMOD, Log, All);

View File

@ -0,0 +1,53 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODStudioStyle.h"
#include "Styling/SlateStyleRegistry.h"
#include "EditorStyle/Public/Interfaces/IEditorStyleModule.h"
#include "Modules/ModuleManager.h"
//////////////////////////////////////////////////////////////////////////
// FFMODStudioStyle
TSharedPtr<FSlateStyleSet> FFMODStudioStyle::StyleInstance = NULL;
void FFMODStudioStyle::Initialize()
{
if (!StyleInstance.IsValid())
{
StyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
}
}
#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(Style->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
TSharedRef<FSlateStyleSet> FFMODStudioStyle::Create()
{
TSharedRef<FSlateStyleSet> Style = MakeShared<FSlateStyleSet>(TEXT("FMODStudioStyle"));
Style->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate"));
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon64x64(64.0f, 64.0f);
Style->Set("ClassIcon.FMODAmbientSound", new IMAGE_BRUSH(TEXT("Icons/AssetIcons/AmbientSound_16x"), Icon16x16));
Style->Set("ClassThumbnail.FMODAmbientSound", new IMAGE_BRUSH(TEXT("Icons/AssetIcons/AmbientSound_64x"), Icon64x64));
Style->Set("ClassIcon.FMODAudioComponent", new IMAGE_BRUSH(TEXT("Icons/ActorIcons/SoundActor_16x"), Icon16x16));
Style->Set("ClassIcon.FMODAsset", new IMAGE_BRUSH(TEXT("Icons/ActorIcons/SoundActor_16x"), Icon16x16));
return Style;
}
#undef IMAGE_BRUSH
void FFMODStudioStyle::Shutdown()
{
ensureMsgf(StyleInstance.IsValid(), TEXT("%S called, but StyleInstance wasn't initialized"), __FUNCTION__);
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
ensure(StyleInstance.IsUnique());
StyleInstance.Reset();
}
//////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,24 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "SlateCore/Public/Styling/SlateStyle.h"
#include "EditorStyle/Public/EditorStyleSet.h"
class FFMODStudioStyle
{
public:
static void Initialize();
static FName GetStyleSetName();
static void Shutdown();
private:
static TSharedRef<class FSlateStyleSet> Create();
private:
static TSharedPtr<class FSlateStyleSet> StyleInstance;
private:
FFMODStudioStyle() {}
};

View File

@ -0,0 +1,285 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "SFMODEventEditorPanel.h"
#include "FMODStudioModule.h"
#include "FMODUtils.h"
#include "Input/Reply.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Layout/SExpandableArea.h"
#include "EditorStyle/Public/EditorStyleSet.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SScrollBox.h"
#include "fmod_studio.hpp"
#define LOCTEXT_NAMESPACE "FMODEventEditor"
SFMODEventEditorPanel::~SFMODEventEditorPanel()
{
}
void SFMODEventEditorPanel::Construct(const FArguments &InArgs)
{
FMODEventEditorPtr = InArgs._FMODEventEditor;
FMOD::Studio::EventDescription *EventDescription = FMODEventEditorPtr.Pin()->GetEventDescription();
TSharedRef<SBorder> ToolbarBorder = ConstructToolbar(EventDescription);
TSharedRef<SExpandableArea> InfoArea = ConstructInfo(EventDescription);
TSharedRef<SExpandableArea> ParametersArea = ConstructParameters(EventDescription);
TSharedRef<SExpandableArea> UserPropertiesArea = ConstructUserProperties(EventDescription);
TSharedRef<SVerticalBox> ChildWidget = SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[InfoArea] +
SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[ParametersArea] +
SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[UserPropertiesArea];
ChildSlot[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 3.0f)[ToolbarBorder] +
SVerticalBox::Slot().FillHeight(
1.0f)[SNew(SScrollBox) +
SScrollBox::Slot().Padding(0.0f)[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(0.0f)[ChildWidget]]]];
}
TSharedRef<SBorder> SFMODEventEditorPanel::ConstructToolbar(FMOD::Studio::EventDescription *EventDescription)
{
float MinDistance = 0.0f;
float MaxDistance = 0.0f;
int32 EventLengthMS = 0;
bool bIsOneshot = false, bIsStream = false, bIs3D = false;
if (EventDescription != nullptr)
{
EventDescription->getMinMaxDistance(&MinDistance, &MaxDistance);
EventDescription->getLength(&EventLengthMS);
EventDescription->isOneshot(&bIsOneshot);
EventDescription->isStream(&bIsStream);
EventDescription->is3D(&bIs3D);
}
const FTimespan EventLength = FTimespan::FromMilliseconds((double)EventLengthMS);
const FString EventLengthString =
EventLength.GetHours() <= 0 ? EventLength.ToString(TEXT("%m:%s.%f")) : EventLength.ToString(TEXT("%h:%m:%s.%f"));
const FText RadiusText =
FText::Format(LOCTEXT("RadiusFormat", "Distance Attenuation: {0}m to {1}m"), FText::AsNumber(MinDistance), FText::AsNumber(MaxDistance));
const FText LengthText = FText::Format(LOCTEXT("LengthFormat", "Length: {0}"), FText::FromString(EventLengthString));
FText EventInfoText;
if (bIs3D && bIsOneshot)
{
EventInfoText = FText::Format(LOCTEXT("RadiusLengthFormat", "{0} - {1}"), RadiusText, LengthText);
}
else if (!bIs3D && bIsOneshot)
{
EventInfoText = LengthText;
}
else if (bIs3D && !bIsOneshot)
{
EventInfoText = RadiusText;
}
return SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("ToolPanel.GroupBorder"))
.Padding(6.0f)
.Content()[SNew(SHorizontalBox) +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 0.0f, 2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)[SNew(SButton)
.VAlign(VAlign_Center)
.Text(LOCTEXT("Play", "Play"))
.ContentPadding(4)
.OnClicked(this, &SFMODEventEditorPanel::OnClickedPlay)] +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[SNew(SButton).Text(LOCTEXT("Pause", "Pause")).ContentPadding(4).OnClicked(this, &SFMODEventEditorPanel::OnClickedPause)] +
SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)[SNew(SButton)
.VAlign(VAlign_Center)
.Text(LOCTEXT("Stop", "Stop"))
.ContentPadding(4)
.OnClicked(this, &SFMODEventEditorPanel::OnClickedStop)] +
SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(2.0f, 0.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)[SNew(STextBlock).Text(EventInfoText)]];
}
void AddTextField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, const FText &Value)
{
InfoBox->AddSlot().Padding(
4.0f, 3.0f)[SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(0.3f)[SNew(STextBlock).Text(FText::FromString(Name))] +
SHorizontalBox::Slot()[SNew(SEditableText).Text(Value).IsReadOnly(true)]];
}
void AddBoolField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, bool bValue)
{
AddTextField(InfoBox, Name, bValue ? LOCTEXT("True", "True") : LOCTEXT("False", "False"));
}
void AddFloatField(TSharedRef<SVerticalBox> &InfoBox, const TCHAR *Name, float Value)
{
AddTextField(InfoBox, Name, FText::AsNumber(Value));
}
TSharedRef<SExpandableArea> MakeBox(TSharedRef<SVerticalBox> &InfoBox, const FText &Value)
{
return SNew(SExpandableArea)
.AreaTitle(Value)
.InitiallyCollapsed(false)
.BodyContent()[SNew(SBorder).BorderImage(FCoreStyle::Get().GetBrush("NoBorder")).Padding(4.0f).Content()[InfoBox]];
}
TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructInfo(FMOD::Studio::EventDescription *EventDescription)
{
TSharedRef<SVerticalBox> InfoBox = SNew(SVerticalBox);
if (EventDescription != nullptr)
{
FString EventPath = FMODUtils::GetPath(EventDescription);
FGuid Guid = FMODUtils::GetID(EventDescription);
int Length = 0.0f;
float MinDist = 0.0f;
float MaxDist = 0.0f;
EventDescription->getLength(&Length);
EventDescription->getMinMaxDistance(&MinDist, &MaxDist);
bool bOneShot = false;
bool bStream = false;
bool b3D = false;
EventDescription->isOneshot(&bOneShot);
EventDescription->isStream(&bStream);
EventDescription->is3D(&b3D);
AddTextField(InfoBox, TEXT("Path"), FText::FromString(EventPath));
AddTextField(InfoBox, TEXT("Guid"), FText::FromString(Guid.ToString(EGuidFormats::DigitsWithHyphensInBraces)));
AddBoolField(InfoBox, TEXT("OneShot"), bOneShot);
AddBoolField(InfoBox, TEXT("Streaming"), bStream);
AddBoolField(InfoBox, TEXT("3D"), b3D);
AddFloatField(InfoBox, TEXT("Length"), static_cast<float>(Length));
if (b3D)
{
AddFloatField(InfoBox, TEXT("Min Dist"), MinDist);
AddFloatField(InfoBox, TEXT("Max Dist"), MaxDist);
}
}
return MakeBox(InfoBox, LOCTEXT("EventInfo", "Event Info"));
}
TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructParameters(FMOD::Studio::EventDescription *EventDescription)
{
auto EventEditor = FMODEventEditorPtr.Pin();
TSharedRef<SVerticalBox> ParametersBox = SNew(SVerticalBox);
FNumberFormattingOptions Options;
Options.MinimumFractionalDigits = 1;
if (EventDescription != nullptr)
{
int32 ParameterCount;
EventDescription->getParameterDescriptionCount(&ParameterCount);
for (int32 ParamIdx = 0; ParamIdx < ParameterCount; ParamIdx++)
{
FMOD_STUDIO_PARAMETER_DESCRIPTION Parameter;
EventDescription->getParameterDescriptionByIndex(ParamIdx, &Parameter);
EventEditor->AddParameter(Parameter.id, Parameter.minimum);
const FString ParameterName = Parameter.type == FMOD_STUDIO_PARAMETER_GAME_CONTROLLED ? FString(UTF8_TO_TCHAR(Parameter.name)) :
FMODUtils::ParameterTypeToString(Parameter.type);
const FText ToolTipText = FText::Format(LOCTEXT("ParameterTooltipFormat", "{0} (Min Value: {1} - Max Value: {2})"),
FText::FromString(ParameterName), FText::AsNumber(Parameter.minimum, &Options), FText::AsNumber(Parameter.maximum, &Options));
ParametersBox->AddSlot().Padding(4.0f,
2.0f)[SNew(SHorizontalBox).ToolTipText(ToolTipText) +
SHorizontalBox::Slot().FillWidth(0.3f)[SNew(STextBlock).Text(FText::FromString(ParameterName))] +
SHorizontalBox::Slot().MaxWidth(200.0f)[SNew(SNumericEntryBox<float>)
.Value(this, &SFMODEventEditorPanel::GetParameterValue, Parameter.id)
.OnValueChanged(this, &SFMODEventEditorPanel::OnParameterValueChanged, Parameter.id)
.AllowSpin(true)
.MinValue(Parameter.minimum)
.MaxValue(Parameter.maximum)
.MinSliderValue(Parameter.minimum)
.MaxSliderValue(Parameter.maximum)
.Delta(0.01f)]];
}
}
return MakeBox(ParametersBox, LOCTEXT("EventParameters", "Event Parameters"));
}
TSharedRef<SExpandableArea> SFMODEventEditorPanel::ConstructUserProperties(FMOD::Studio::EventDescription *EventDescription)
{
TSharedRef<SVerticalBox> UserPropertiesBox = SNew(SVerticalBox);
if (EventDescription != nullptr)
{
int32 UserPropertyCount;
EventDescription->getUserPropertyCount(&UserPropertyCount);
for (int32 PropertyIdx = 0; PropertyIdx < UserPropertyCount; PropertyIdx++)
{
FMOD_STUDIO_USER_PROPERTY UserProperty;
EventDescription->getUserPropertyByIndex(PropertyIdx, &UserProperty);
FText PropertyText;
switch (UserProperty.type)
{
case FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER:
PropertyText = FText::AsNumber(UserProperty.intvalue);
break;
case FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN:
PropertyText = UserProperty.boolvalue ? LOCTEXT("True", "True") : LOCTEXT("False", "False");
break;
case FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT:
PropertyText = FText::AsNumber(UserProperty.floatvalue);
break;
case FMOD_STUDIO_USER_PROPERTY_TYPE_STRING:
PropertyText = FText::FromString(UTF8_TO_TCHAR(UserProperty.stringvalue));
break;
}
FString UserName(UTF8_TO_TCHAR(UserProperty.name));
AddTextField(UserPropertiesBox, *UserName, PropertyText);
}
}
return MakeBox(UserPropertiesBox, LOCTEXT("EventUserProperties", "Event User Properties"));
}
FReply SFMODEventEditorPanel::OnClickedPlay()
{
FMODEventEditorPtr.Pin()->PlayEvent();
return FReply::Handled();
}
FReply SFMODEventEditorPanel::OnClickedStop()
{
FMODEventEditorPtr.Pin()->StopEvent();
return FReply::Handled();
}
FReply SFMODEventEditorPanel::OnClickedPause()
{
FMODEventEditorPtr.Pin()->PauseEvent();
return FReply::Handled();
}
void SFMODEventEditorPanel::OnParameterValueChanged(float NewValue, FMOD_STUDIO_PARAMETER_ID ParameterId)
{
FMODEventEditorPtr.Pin()->SetParameterValue(ParameterId, NewValue);
}
TOptional<float> SFMODEventEditorPanel::GetParameterValue(FMOD_STUDIO_PARAMETER_ID ParameterId) const
{
return FMODEventEditorPtr.Pin()->GetParameterValue(ParameterId);
}
#undef LOC_NAMESPACE

View File

@ -0,0 +1,42 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "FMODEventEditor.h"
namespace FMOD
{
namespace Studio
{
class EventDescription;
}
}
class SFMODEventEditorPanel : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SFMODEventEditorPanel) {}
SLATE_ARGUMENT(TWeakPtr<FFMODEventEditor>, FMODEventEditor)
SLATE_END_ARGS()
~SFMODEventEditorPanel();
/** SCompoundWidget interface */
void Construct(const FArguments &InArgs);
private:
TSharedRef<SBorder> ConstructToolbar(FMOD::Studio::EventDescription *EventDescription);
TSharedRef<SExpandableArea> ConstructInfo(FMOD::Studio::EventDescription *EventDescription);
TSharedRef<SExpandableArea> ConstructParameters(FMOD::Studio::EventDescription *EventDescription);
TSharedRef<SExpandableArea> ConstructUserProperties(FMOD::Studio::EventDescription *EventDescription);
/** Editor that owns this panel */
TWeakPtr<FFMODEventEditor> FMODEventEditorPtr;
FReply OnClickedPlay();
FReply OnClickedStop();
FReply OnClickedPause();
TOptional<float> GetParameterValue(FMOD_STUDIO_PARAMETER_ID ParameterId) const;
void OnParameterValueChanged(float NewValue, FMOD_STUDIO_PARAMETER_ID ParameterId);
};

View File

@ -0,0 +1,185 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "Sequencer/FMODChannelEditors.h"
#include "ISequencerChannelInterface.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "MovieSceneTimeHelpers.h"
#include "MovieSceneToolHelpers.h"
#include "ScopedTransaction.h"
#include "EditorWidgets/Public/SEnumCombo.h"
#include "EditorStyleSet.h"
#include "Channels/MovieSceneChannelTraits.h"
class SFMODEventControlKeyEditor : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SFMODEventControlKeyEditor) {}
SLATE_END_ARGS();
void Construct(const FArguments &InArgs, TMovieSceneChannelHandle<FFMODEventControlChannel> InChannelHandle,
TWeakObjectPtr<UMovieSceneSection> InWeakSection, TWeakPtr<ISequencer> InWeakSequencer, UEnum *InEnum)
{
ChannelHandle = InChannelHandle;
WeakSection = InWeakSection;
WeakSequencer = InWeakSequencer;
ChildSlot[MovieSceneToolHelpers::MakeEnumComboBox(InEnum,
TAttribute<int32>::Create(TAttribute<int32>::FGetter::CreateSP(this, &SFMODEventControlKeyEditor::OnGetCurrentValueAsInt)),
SEnumComboBox::FOnEnumSelectionChanged::CreateSP(this, &SFMODEventControlKeyEditor::OnChangeKey))];
}
private:
int32 OnGetCurrentValueAsInt() const
{
using namespace UE::MovieScene;
FFMODEventControlChannel *Channel = ChannelHandle.Get();
ISequencer *Sequencer = WeakSequencer.Pin().Get();
UMovieSceneSection *OwningSection = WeakSection.Get();
uint8 Result = 0;
if (Channel && Sequencer && OwningSection)
{
const FFrameTime CurrentTime = UE::MovieScene::ClampToDiscreteRange(Sequencer->GetLocalTime().Time, OwningSection->GetRange());
EvaluateChannel(Channel, CurrentTime, Result);
}
return Result;
}
void SetValue(uint8 InValue)
{
using namespace UE::MovieScene;
UMovieSceneSection *OwningSection = WeakSection.Get();
if (!OwningSection)
{
return;
}
OwningSection->SetFlags(RF_Transactional);
FFMODEventControlChannel *Channel = ChannelHandle.Get();
ISequencer *Sequencer = WeakSequencer.Pin().Get();
if (!OwningSection->TryModify() || !Channel || !Sequencer)
{
return;
}
const FFrameNumber CurrentTime = Sequencer->GetLocalTime().Time.FloorToFrame();
const bool bAutoSetTrackDefaults = Sequencer->GetAutoSetTrackDefaults();
EMovieSceneKeyInterpolation Interpolation = Sequencer->GetKeyInterpolation();
TArray<FKeyHandle> KeysAtCurrentTime;
Channel->GetKeys(TRange<FFrameNumber>(CurrentTime), nullptr, &KeysAtCurrentTime);
if (KeysAtCurrentTime.Num() > 0)
{
AssignValue(Channel, KeysAtCurrentTime[0], InValue);
}
else
{
const bool bHasAnyKeys = Channel->GetNumKeys() != 0;
if (bHasAnyKeys || bAutoSetTrackDefaults == false)
{
// When auto setting track defaults are disabled, add a key even when it's empty so that the changed
// value is saved and is propagated to the property.
AddKeyToChannel(Channel, CurrentTime, InValue, Interpolation);
}
if (bHasAnyKeys)
{
TRange<FFrameNumber> KeyRange = TRange<FFrameNumber>(CurrentTime);
TRange<FFrameNumber> SectionRange = OwningSection->GetRange();
if (!SectionRange.Contains(KeyRange))
{
OwningSection->SetRange(TRange<FFrameNumber>::Hull(KeyRange, SectionRange));
}
}
}
// Always update the default value when auto-set default values is enabled so that the last changes
// are always saved to the track.
if (bAutoSetTrackDefaults)
{
SetChannelDefault(Channel, InValue);
}
}
void OnChangeKey(int32 Selection, ESelectInfo::Type SelectionType)
{
FScopedTransaction Transaction(FText::FromString("Set FMOD Event Control Key Value"));
SetValue(Selection);
if (ISequencer *Sequencer = WeakSequencer.Pin().Get())
{
Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
}
}
TMovieSceneChannelHandle<FFMODEventControlChannel> ChannelHandle;
TWeakObjectPtr<UMovieSceneSection> WeakSection;
TWeakPtr<ISequencer> WeakSequencer;
};
bool CanCreateKeyEditor(const FFMODEventControlChannel *Channel)
{
return true;
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FFMODEventControlChannel> &Channel, UMovieSceneSection *Section,
const FGuid &InObjectBindingID, TWeakPtr<FTrackInstancePropertyBindings> PropertyBindings, TWeakPtr<ISequencer> InSequencer)
{
const FFMODEventControlChannel *RawChannel = Channel.Get();
if (!RawChannel)
{
return SNullWidget::NullWidget;
}
UEnum *Enum = RawChannel->GetEnum();
return SNew(SFMODEventControlKeyEditor, Channel, Section, InSequencer, Enum);
}
void DrawKeys(FFMODEventControlChannel *Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
static const FName KeyLeftBrushName("Sequencer.KeyLeft");
static const FName KeyRightBrushName("Sequencer.KeyRight");
static const FName KeyDiamondBrushName("Sequencer.KeyDiamond");
const FSlateBrush *LeftKeyBrush = FAppStyle::GetBrush(KeyLeftBrushName);
const FSlateBrush *RightKeyBrush = FAppStyle::GetBrush(KeyRightBrushName);
const FSlateBrush *DiamondBrush = FAppStyle::GetBrush(KeyDiamondBrushName);
TMovieSceneChannelData<uint8> ChannelData = Channel->GetData();
for (int32 Index = 0; Index < InKeyHandles.Num(); ++Index)
{
FKeyHandle Handle = InKeyHandles[Index];
FKeyDrawParams Params;
Params.BorderBrush = Params.FillBrush = DiamondBrush;
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE)
{
const EFMODEventControlKey Value = (EFMODEventControlKey)ChannelData.GetValues()[KeyIndex];
if (Value == EFMODEventControlKey::Play)
{
Params.BorderBrush = Params.FillBrush = LeftKeyBrush;
Params.FillOffset = FVector2D(-1.0f, 1.0f);
}
else if (Value == EFMODEventControlKey::Stop)
{
Params.BorderBrush = Params.FillBrush = RightKeyBrush;
Params.FillOffset = FVector2D(1.0f, 1.0f);
}
}
OutKeyDrawParams[Index] = Params;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "MVVM/Views/KeyDrawParams.h"
#include "Channels/MovieSceneChannelHandle.h"
#include "Sequencer/FMODEventControlSection.h"
/** Key editor overrides */
bool CanCreateKeyEditor(const FFMODEventControlChannel* Channel);
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FFMODEventControlChannel>& Channel, UMovieSceneSection* Section,
const FGuid& InObjectBindingID, TWeakPtr<FTrackInstancePropertyBindings> PropertyBindings, TWeakPtr<ISequencer> InSequencer);
/** Key drawing overrides */
void DrawKeys(FFMODEventControlChannel *Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams);

View File

@ -0,0 +1,194 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODEventControlTrackEditor.h"
#include "Rendering/DrawElements.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Curves/IntegralCurve.h"
#include "SequencerSectionPainter.h"
#include "EditorStyleSet.h"
#include "Editor/UnrealEdEngine.h"
#include "Sequencer/FMODEventControlSection.h"
#include "Sequencer/FMODEventControlTrack.h"
#include "ISectionLayoutBuilder.h"
#include "FMODAmbientSound.h"
#include "CommonMovieSceneTools.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "Channels/MovieSceneChannelEditorData.h"
#define LOCTEXT_NAMESPACE "FFMODEventControlTrackEditor"
FFMODEventControlSection::FFMODEventControlSection(UMovieSceneSection &InSection, TSharedRef<ISequencer> InOwningSequencer)
: Section(InSection)
, OwningSequencerPtr(InOwningSequencer)
{
}
UMovieSceneSection *FFMODEventControlSection::GetSectionObject()
{
return &Section;
}
float FFMODEventControlSection::GetSectionHeight() const
{
static const float SectionHeight = 20.f;
return SectionHeight;
}
int32 FFMODEventControlSection::OnPaintSection(FSequencerSectionPainter &InPainter) const
{
TSharedPtr<ISequencer> OwningSequencer = OwningSequencerPtr.Pin();
if (!OwningSequencer.IsValid())
{
return InPainter.LayerId + 1;
}
const ESlateDrawEffect DrawEffects = InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
const FTimeToPixel &TimeToPixelConverter = InPainter.GetTimeConverter();
FLinearColor TrackColor;
// TODO: Set / clip stop time based on event length
UFMODEventControlSection *ControlSection = Cast<UFMODEventControlSection>(&Section);
if (IsValid(ControlSection))
{
UFMODEventControlTrack *ParentTrack = Cast<UFMODEventControlTrack>(ControlSection->GetOuter());
if (IsValid(ParentTrack))
{
TrackColor = ParentTrack->GetColorTint();
}
}
// TODO: This should only draw the visible ranges.
TArray<TRange<float>> DrawRanges;
TOptional<float> CurrentRangeStart;
if (ControlSection != nullptr)
{
TMovieSceneChannelData<const uint8> ChannelData = ControlSection->ControlKeys.GetData();
TArrayView<const FFrameNumber> Times = ChannelData.GetTimes();
TArrayView<const uint8> Values = ChannelData.GetValues();
for (int32 Index = 0; Index < Times.Num(); ++Index)
{
const double Time = Times[Index] / TimeToPixelConverter.GetTickResolution();
const EFMODEventControlKey Value = (EFMODEventControlKey)Values[Index];
if (Value == EFMODEventControlKey::Play)
{
if (CurrentRangeStart.IsSet() == false)
{
CurrentRangeStart = Time;
}
}
if (Value == EFMODEventControlKey::Stop)
{
if (CurrentRangeStart.IsSet())
{
DrawRanges.Add(TRange<float>(CurrentRangeStart.GetValue(), Time));
CurrentRangeStart.Reset();
}
}
}
}
if (CurrentRangeStart.IsSet())
{
DrawRanges.Add(TRange<float>(CurrentRangeStart.GetValue(), OwningSequencer->GetViewRange().GetUpperBoundValue()));
}
for (const TRange<float> &DrawRange : DrawRanges)
{
float XOffset = TimeToPixelConverter.SecondsToPixel(DrawRange.GetLowerBoundValue());
float XSize = TimeToPixelConverter.SecondsToPixel(DrawRange.GetUpperBoundValue()) - XOffset;
FSlateDrawElement::MakeBox(InPainter.DrawElements, InPainter.LayerId,
InPainter.SectionGeometry.ToPaintGeometry(
FVector2D(XOffset, (InPainter.SectionGeometry.GetLocalSize().Y - SequencerSectionConstants::KeySize.Y) / 2),
FVector2D(XSize, SequencerSectionConstants::KeySize.Y)),
FAppStyle::GetBrush("Sequencer.Section.Background"), DrawEffects);
FSlateDrawElement::MakeBox(InPainter.DrawElements, InPainter.LayerId,
InPainter.SectionGeometry.ToPaintGeometry(
FVector2D(XOffset, (InPainter.SectionGeometry.GetLocalSize().Y - SequencerSectionConstants::KeySize.Y) / 2),
FVector2D(XSize, SequencerSectionConstants::KeySize.Y)),
FAppStyle::GetBrush("Sequencer.Section.BackgroundTint"), DrawEffects, TrackColor);
}
return InPainter.LayerId + 1;
}
FFMODEventControlTrackEditor::FFMODEventControlTrackEditor(TSharedRef<ISequencer> InSequencer)
: FMovieSceneTrackEditor(InSequencer)
{
}
TSharedRef<ISequencerTrackEditor> FFMODEventControlTrackEditor::CreateTrackEditor(TSharedRef<ISequencer> InSequencer)
{
return MakeShareable(new FFMODEventControlTrackEditor(InSequencer));
}
bool FFMODEventControlTrackEditor::SupportsType(TSubclassOf<UMovieSceneTrack> Type) const
{
return Type == UFMODEventControlTrack::StaticClass();
}
TSharedRef<ISequencerSection> FFMODEventControlTrackEditor::MakeSectionInterface(
UMovieSceneSection &SectionObject, UMovieSceneTrack &Track, FGuid ObjectBinding)
{
check(SupportsType(SectionObject.GetOuter()->GetClass()));
const TSharedPtr<ISequencer> OwningSequencer = GetSequencer();
return MakeShareable(new FFMODEventControlSection(SectionObject, OwningSequencer.ToSharedRef()));
}
void FFMODEventControlTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder &MenuBuilder, const TArray<FGuid> &ObjectBindings, const UClass *ObjectClass)
{
if (ObjectClass->IsChildOf(AFMODAmbientSound::StaticClass()) || ObjectClass->IsChildOf(UFMODAudioComponent::StaticClass()))
{
const TSharedPtr<ISequencer> ParentSequencer = GetSequencer();
MenuBuilder.AddMenuEntry(LOCTEXT("AddFMODEventControlTrack", "FMOD Event Control Track"),
LOCTEXT("FMODEventControlTooltip", "Adds a track for controlling FMOD event."), FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FFMODEventControlTrackEditor::AddControlKey, ObjectBindings)));
}
}
void FFMODEventControlTrackEditor::AddControlKey(TArray<FGuid> ObjectGuids)
{
TSharedPtr<ISequencer> SequencerPtr = GetSequencer();
for (FGuid ObjectGuid : ObjectGuids)
{
UObject *Object = SequencerPtr.IsValid() ? SequencerPtr->FindSpawnedObjectOrTemplate(ObjectGuid) : nullptr;
if (Object)
{
AnimatablePropertyChanged(FOnKeyProperty::CreateRaw(this, &FFMODEventControlTrackEditor::AddKeyInternal, Object));
}
}
}
FKeyPropertyResult FFMODEventControlTrackEditor::AddKeyInternal(FFrameNumber KeyTime, UObject *Object)
{
FKeyPropertyResult KeyPropertyResult;
FFindOrCreateHandleResult HandleResult = FindOrCreateHandleToObject(Object);
FGuid ObjectHandle = HandleResult.Handle;
KeyPropertyResult.bHandleCreated |= HandleResult.bWasCreated;
if (ObjectHandle.IsValid())
{
FFindOrCreateTrackResult TrackResult = FindOrCreateTrackForObject(ObjectHandle, UFMODEventControlTrack::StaticClass());
UMovieSceneTrack *Track = TrackResult.Track;
KeyPropertyResult.bTrackCreated |= TrackResult.bWasCreated;
if (KeyPropertyResult.bTrackCreated && ensure(Track))
{
UFMODEventControlTrack *EventTrack = Cast<UFMODEventControlTrack>(Track);
EventTrack->AddNewSection(KeyTime);
EventTrack->SetDisplayName(LOCTEXT("TrackName", "FMOD Event"));
KeyPropertyResult.bTrackModified = true;
}
}
return KeyPropertyResult;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,59 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "CoreMinimal.h"
#include "Misc/Guid.h"
#include "Templates/SubclassOf.h"
#include "Curves/KeyHandle.h"
#include "ISequencer.h"
#include "MovieSceneTrack.h"
#include "ISequencerSection.h"
#include "ISequencerTrackEditor.h"
#include "MovieSceneTrackEditor.h"
class FMenuBuilder;
class FSequencerSectionPainter;
/** FMOD Event control track */
class FFMODEventControlTrackEditor : public FMovieSceneTrackEditor
{
public:
FFMODEventControlTrackEditor(TSharedRef<ISequencer> InSequencer);
static TSharedRef<ISequencerTrackEditor> CreateTrackEditor(TSharedRef<ISequencer> OwningSequencer);
void AddControlKey(TArray<FGuid> ObjectGuids);
// Begin ISequencerTrackEditor interface
virtual void BuildObjectBindingTrackMenu(FMenuBuilder &MenuBuilder, const TArray<FGuid> &ObjectBindings, const UClass *ObjectClass) override;
virtual TSharedRef<ISequencerSection> MakeSectionInterface(
UMovieSceneSection &SectionObject, UMovieSceneTrack &Track, FGuid ObjectBinding) override;
virtual bool SupportsType(TSubclassOf<UMovieSceneTrack> Type) const override;
// End ISequencerTrackEditor interface
private:
/** Delegate for AnimatablePropertyChanged in AddKey. */
virtual FKeyPropertyResult AddKeyInternal(FFrameNumber KeyTime, UObject *Object);
};
/** Class for event control sections. */
class FFMODEventControlSection : public ISequencerSection, public TSharedFromThis<FFMODEventControlSection>
{
public:
FFMODEventControlSection(UMovieSceneSection &InSection, TSharedRef<ISequencer> InOwningSequencer);
// Begin ISequencerSection interface
virtual UMovieSceneSection *GetSectionObject() override;
virtual float GetSectionHeight() const override;
virtual int32 OnPaintSection(FSequencerSectionPainter &InPainter) const override;
virtual bool SectionIsResizable() const override { return false; }
// End ISequencerSection interface
private:
/** The section we are visualizing. */
UMovieSceneSection &Section;
/** The sequencer that owns this section */
TWeakPtr<ISequencer> OwningSequencerPtr;
};

View File

@ -0,0 +1,156 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODEventParameterTrackEditor.h"
#include "FMODAmbientSound.h"
#include "FMODEvent.h"
#include "FMODStudioModule.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Sequencer/FMODEventParameterTrack.h"
#include "Sections/MovieSceneParameterSection.h"
#include "Sequencer/FMODParameterSection.h"
#include "SequencerUtilities.h"
#include "fmod_studio.hpp"
#define LOCTEXT_NAMESPACE "FMODEeventParameterTrackEditor"
FName FFMODEventParameterTrackEditor::TrackName("FMODEventParameter");
FFMODEventParameterTrackEditor::FFMODEventParameterTrackEditor(TSharedRef<ISequencer> InSequencer)
: FMovieSceneTrackEditor(InSequencer)
{
}
TSharedRef<ISequencerTrackEditor> FFMODEventParameterTrackEditor::CreateTrackEditor(TSharedRef<ISequencer> OwningSequencer)
{
return MakeShareable(new FFMODEventParameterTrackEditor(OwningSequencer));
}
TSharedRef<ISequencerSection> FFMODEventParameterTrackEditor::MakeSectionInterface(
UMovieSceneSection &SectionObject, UMovieSceneTrack &Track, FGuid ObjectBinding)
{
UMovieSceneParameterSection *ParameterSection = Cast<UMovieSceneParameterSection>(&SectionObject);
checkf(ParameterSection != nullptr, TEXT("Unsupported section type."));
return MakeShareable(new FFMODParameterSection(*ParameterSection));
}
TSharedPtr<SWidget> FFMODEventParameterTrackEditor::BuildOutlinerEditWidget(
const FGuid &ObjectBinding, UMovieSceneTrack *Track, const FBuildEditWidgetParams &Params)
{
UFMODEventParameterTrack *EventParameterTrack = Cast<UFMODEventParameterTrack>(Track);
// Create a container edit box
return FSequencerUtilities::MakeAddButton(LOCTEXT("ParameterText", "Parameter"),
FOnGetContent::CreateSP(this, &FFMODEventParameterTrackEditor::OnGetAddParameterMenuContent, ObjectBinding, EventParameterTrack),
Params.NodeIsHovered,
GetSequencer());
}
void FFMODEventParameterTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder &MenuBuilder, const TArray<FGuid> &ObjectBindings, const UClass *ObjectClass)
{
if (ObjectClass->IsChildOf(AFMODAmbientSound::StaticClass()) || ObjectClass->IsChildOf(UFMODAudioComponent::StaticClass()))
{
const TSharedPtr<ISequencer> ParentSequencer = GetSequencer();
MenuBuilder.AddMenuEntry(LOCTEXT("AddFMODParameterTrack", "FMOD Event Parameter Track"),
LOCTEXT("AddFMODParameterTrackTooltip", "Adds a track for controlling FMOD event parameter values."), FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FFMODEventParameterTrackEditor::AddEventParameterTrack, ObjectBindings[0]),
FCanExecuteAction::CreateSP(this, &FFMODEventParameterTrackEditor::CanAddEventParameterTrack, ObjectBindings[0])));
}
}
bool FFMODEventParameterTrackEditor::SupportsType(TSubclassOf<UMovieSceneTrack> Type) const
{
return Type == UFMODEventParameterTrack::StaticClass();
}
TSharedRef<SWidget> FFMODEventParameterTrackEditor::OnGetAddParameterMenuContent(FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack)
{
TSharedPtr<ISequencer> SequencerPtr = GetSequencer();
AFMODAmbientSound *Sound = SequencerPtr.IsValid() ? Cast<AFMODAmbientSound>(SequencerPtr->FindSpawnedObjectOrTemplate(ObjectBinding)) : nullptr;
UFMODAudioComponent *AudioComponent;
if (IsValid(Sound))
{
AudioComponent = Sound->AudioComponent;
}
else
{
AudioComponent = SequencerPtr.IsValid() ? Cast<UFMODAudioComponent>(SequencerPtr->FindSpawnedObjectOrTemplate(ObjectBinding)) : nullptr;
}
return BuildParameterMenu(ObjectBinding, EventParameterTrack, AudioComponent);
}
TSharedRef<SWidget> FFMODEventParameterTrackEditor::BuildParameterMenu(
FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack, UFMODAudioComponent *AudioComponent)
{
FMenuBuilder AddParameterMenuBuilder(true, nullptr);
if (IsValid(AudioComponent) && AudioComponent->Event)
{
TArray<FParameterNameAndAction> ParameterNamesAndActions;
TArray<FMOD_STUDIO_PARAMETER_DESCRIPTION> ParameterDescriptions;
AudioComponent->Event->GetParameterDescriptions(ParameterDescriptions);
for (FMOD_STUDIO_PARAMETER_DESCRIPTION &ParameterDescription : ParameterDescriptions)
{
FName ParameterName(ParameterDescription.name);
FExecuteAction InitAction =
FExecuteAction::CreateSP(this, &FFMODEventParameterTrackEditor::AddParameter, ObjectBinding, EventParameterTrack, ParameterName);
FUIAction AddParameterMenuAction(InitAction);
FParameterNameAndAction NameAndAction(ParameterName, AddParameterMenuAction);
ParameterNamesAndActions.Add(NameAndAction);
}
// Sort and generate menu.
ParameterNamesAndActions.Sort();
for (FParameterNameAndAction NameAndAction : ParameterNamesAndActions)
{
AddParameterMenuBuilder.AddMenuEntry(FText::FromName(NameAndAction.ParameterName), FText(), FSlateIcon(), NameAndAction.Action);
}
}
return AddParameterMenuBuilder.MakeWidget();
}
bool FFMODEventParameterTrackEditor::CanAddEventParameterTrack(FGuid ObjectBinding)
{
return GetSequencer()->GetFocusedMovieSceneSequence()->GetMovieScene()->FindTrack(
UFMODEventParameterTrack::StaticClass(), ObjectBinding, TrackName) == nullptr;
}
void FFMODEventParameterTrackEditor::AddEventParameterTrack(FGuid ObjectBinding)
{
FindOrCreateTrackForObject(ObjectBinding, UFMODEventParameterTrack::StaticClass(), TrackName, true);
GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
}
void FFMODEventParameterTrackEditor::AddParameter(FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack, FName ParameterName)
{
UMovieSceneSequence *MovieSceneSequence = GetMovieSceneSequence();
FFrameNumber KeyTime = GetTimeForKey();
for (TWeakObjectPtr<> Object : GetSequencer()->FindObjectsInCurrentSequence(ObjectBinding))
{
AFMODAmbientSound *Sound = Cast<AFMODAmbientSound>(Object.Get());
UFMODAudioComponent *AudioComponent = nullptr;
if (IsValid(Sound))
{
AudioComponent = Sound->AudioComponent;
}
else
{
AudioComponent = Cast<UFMODAudioComponent>(Object.Get());
}
if (IsValid(AudioComponent))
{
float Value = AudioComponent->GetParameter(ParameterName);
const FScopedTransaction Transaction(LOCTEXT("AddEventParameter", "Add event parameter"));
EventParameterTrack->Modify();
EventParameterTrack->AddParameterKey(ParameterName, KeyTime, Value);
}
}
GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,77 @@
// Taken from ParticleParameterTrackEditor
#pragma once
#include "CoreMinimal.h"
#include "Misc/Guid.h"
#include "Templates/SubclassOf.h"
#include "Widgets/SWidget.h"
#include "ISequencer.h"
#include "MovieSceneTrack.h"
#include "ISequencerSection.h"
#include "Framework/Commands/UIAction.h"
#include "ISequencerTrackEditor.h"
#include "MovieSceneTrackEditor.h"
class FMenuBuilder;
class UFMODEventParameterTrack;
class UFMODAudioComponent;
struct FMOD_STUDIO_PARAMETER_DESCRIPTION;
/**
* Track editor for material parameters.
*/
class FFMODEventParameterTrackEditor : public FMovieSceneTrackEditor
{
public:
/** Constructor. */
FFMODEventParameterTrackEditor(TSharedRef<ISequencer> InSequencer);
/** Virtual destructor. */
virtual ~FFMODEventParameterTrackEditor() {}
/**
* Creates an instance of this class. Called by a sequencer.
*
* @param OwningSequencer The sequencer instance to be used by this tool.
* @return The new instance of this class.
*/
static TSharedRef<ISequencerTrackEditor> CreateTrackEditor(TSharedRef<ISequencer> OwningSequencer);
// ISequencerTrackEditor interface
virtual TSharedPtr<SWidget> BuildOutlinerEditWidget(
const FGuid &ObjectBinding, UMovieSceneTrack *Track, const FBuildEditWidgetParams &Params) override;
virtual TSharedRef<ISequencerSection> MakeSectionInterface(
UMovieSceneSection &SectionObject, UMovieSceneTrack &Track, FGuid ObjectBinding) override;
virtual bool SupportsType(TSubclassOf<UMovieSceneTrack> Type) const override;
private:
static FName TrackName;
// Struct used for building the parameter menu.
struct FParameterNameAndAction
{
FName ParameterName;
FUIAction Action;
FParameterNameAndAction(FName InParameterName, FUIAction InAction)
{
ParameterName = InParameterName;
Action = InAction;
}
bool operator<(FParameterNameAndAction const &Other) const { return ParameterName.FastLess(Other.ParameterName); }
};
void BuildObjectBindingTrackMenu(FMenuBuilder &MenuBuilder, const TArray<FGuid> &ObjectBindings, const UClass *ObjectClass);
/** Provides the contents of the add parameter menu. */
TSharedRef<SWidget> OnGetAddParameterMenuContent(FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack);
TSharedRef<SWidget> BuildParameterMenu(FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack, UFMODAudioComponent *AudioComponent);
bool CanAddEventParameterTrack(FGuid ObjectBinding);
void AddEventParameterTrack(FGuid ObjectBinding);
/** Adds an event parameter and initial key to a track. */
void AddParameter(FGuid ObjectBinding, UFMODEventParameterTrack *EventParameterTrack, FName ParameterName);
};

View File

@ -0,0 +1,25 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#include "FMODParameterSection.h"
#include "ISectionLayoutBuilder.h"
#include "ScopedTransaction.h"
#include "Sections/MovieSceneParameterSection.h"
#define LOCTEXT_NAMESPACE "FMODParameterSection"
bool FFMODParameterSection::RequestDeleteKeyArea(const TArray<FName> &KeyAreaNamePath)
{
if (KeyAreaNamePath.Num() == 1)
{
const FScopedTransaction Transaction(LOCTEXT("DeleteEventParameter", "Delete event parameter"));
UMovieSceneParameterSection *ParameterSection = Cast<UMovieSceneParameterSection>(WeakSection.Get());
if (ParameterSection->TryModify())
{
return ParameterSection->RemoveScalarParameter(KeyAreaNamePath[0]);
}
}
return true;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,21 @@
// Copyright (c), Firelight Technologies Pty, Ltd. 2012-2023.
#pragma once
#include "ISequencerSection.h"
class UMovieSceneSection;
/** A movie scene section for Event parameters. */
class FFMODParameterSection : public FSequencerSection
{
public:
FFMODParameterSection(UMovieSceneSection &InSectionObject)
: FSequencerSection(InSectionObject)
{
}
// Begin ISequencerSection interface
virtual bool RequestDeleteKeyArea(const TArray<FName> &KeyAreaNamePath) override;
// End ISequencerSection interface
};