diff --git a/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h b/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h index 51623bc0..ad0db42b 100755 --- a/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h +++ b/FMODStudio/Source/FMODStudio/Classes/FMODAudioComponent.h @@ -206,14 +206,26 @@ class FMODSTUDIO_API UFMODAudioComponent : public USceneComponent UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") void SetVolume(float volume); + /** Gets the volume level. */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") + float GetVolume(); + /** Sets the pitch multiplier. */ UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") void SetPitch(float pitch); + /** Gets the pitch multiplier. */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") + float GetPitch(); + /** Pause/Unpause an audio component. */ UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") void SetPaused(bool paused); + /** Get Pause/Unpause state of an audio component. */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") + bool GetPaused(); + /** Set a parameter of the Event. */ UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|Components") void SetParameter(FName Name, float Value); diff --git a/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h b/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h index 89242911..6bc5852c 100755 --- a/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h +++ b/FMODStudio/Source/FMODStudio/Classes/FMODBlueprintStatics.h @@ -178,6 +178,12 @@ class FMODSTUDIO_API UFMODBlueprintStatics : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|VCA", meta = (UnsafeDuringActorConstruction = "true")) static void VCASetVolume(class UFMODVCA *Vca, float Volume); + /** Get volume on a VCA + * @param Vca - VCA to use + */ + UFUNCTION(BlueprintCallable, Category = "Audio|FMOD|VCA", meta = (UnsafeDuringActorConstruction = "true")) + static float VCAGetVolume(class UFMODVCA *Vca); + /** Set a global parameter from the System. * @param Name - Name of parameter * @param Value - Value of parameter @@ -319,5 +325,5 @@ class FMODSTUDIO_API UFMODBlueprintStatics : public UBlueprintFunctionLibrary /** Set the active locale for subsequent bank loads. */ UFUNCTION(BlueprintCallable, Category = "Audio|FMOD") - static void SetLocale(const FString& Locale); + static bool SetLocale(const FString& Locale); }; diff --git a/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h b/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h index 1c7732cd..c143f138 100755 --- a/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h +++ b/FMODStudio/Source/FMODStudio/Classes/FMODSettings.h @@ -94,6 +94,42 @@ struct FFMODProjectLocale bool bDefault; }; +USTRUCT() +struct FFMODPerPlatformConfig +{ + GENERATED_USTRUCT_BODY() + /** + * Sample rate to use, or 0 to match system rate. + */ + + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 SampleRate = 48000; + + /** Project Output Format, should match the mode set up for the Studio project. */ + UPROPERTY(config, EditAnywhere, Category = Basic) + TEnumAsByte OutputFormat = EFMODSpeakerMode::Surround_5_1; + + /** + * Whether to match hardware sample rate where reasonable (44.1kHz to 48kHz). + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + bool bMatchHardwareSampleRate = true; + + /** + * DSP mixer buffer length (eg. 512, 1024) or 0 for system default. + * When changing the Buffer Length, Buffer Count also needs to be set. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 DSPBufferLength = 0; + + /** + * DSP mixer buffer count (eg. 2, 4) or 0 for system default. + * When changing the Buffer Count, Buffer Length also needs to be set. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + int32 DSPBufferCount = 0; +}; + UCLASS(config = Engine, defaultconfig) class FMODSTUDIO_API UFMODSettings : public UObject { @@ -130,10 +166,6 @@ class FMODSTUDIO_API UFMODSettings : public UObject UPROPERTY(config, EditAnywhere, Category = Basic, meta = (RelativeToGameContentDir)) FDirectoryPath BankOutputDirectory; - /** Project Output Format, should match the mode set up for the Studio project. */ - UPROPERTY(config, EditAnywhere, Category = Basic) - TEnumAsByte OutputFormat; - /** * Locales for localized banks. These should match the project locales configured in the FMOD Studio project. */ @@ -152,17 +184,23 @@ class FMODSTUDIO_API UFMODSettings : public UObject UPROPERTY(config, EditAnywhere, Category = InitSettings) float Vol0VirtualLevel; - /** - * Sample rate to use, or 0 to match system rate. + /** + * Scaling factor for doppler shift. Default = 1.0. */ - UPROPERTY(config, EditAnywhere, Category = InitSettings) - int32 SampleRate; + UPROPERTY(config, EditAnywhere, Category = InitSettings) + float DopplerScale; - /** - * Whether to match hardware sample rate where reasonable (44.1kHz to 48kHz). - */ - UPROPERTY(config, EditAnywhere, Category = InitSettings) - bool bMatchHardwareSampleRate; + /** + * Relative distance factor to FMOD's units. Default = 1.0. (1.0 = 1 metre). + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + float DistanceFactor; + + /** + * Scaling factor for 3D sound rolloff or attenuation for FMOD_3D_INVERSEROLLOFF based sounds only (which is the default type). Default = 1.0. + */ + UPROPERTY(config, EditAnywhere, Category = InitSettings) + float RolloffScale; /** * Number of actual software voices that can be used at once. @@ -176,19 +214,17 @@ class FMODSTUDIO_API UFMODSettings : public UObject UPROPERTY(config, EditAnywhere, Category = InitSettings) int32 TotalChannelCount; - /** - * DSP mixer buffer length (eg. 512, 1024) or 0 for system default. - * When changing the Buffer Length, Buffer Count also needs to be set. - */ - UPROPERTY(config, EditAnywhere, Category = InitSettings) - int32 DSPBufferLength; - - /** - * DSP mixer buffer count (eg. 2, 4) or 0 for system default. - * When changing the Buffer Count, Buffer Length also needs to be set. - */ - UPROPERTY(config, EditAnywhere, Category = InitSettings) - int32 DSPBufferCount; + UPROPERTY(config, EditAnywhere, Category = PerPlatformSettings) + FFMODPerPlatformConfig DefaultPlatformSettings; + + UPROPERTY(config, EditAnywhere, Category = PerPlatformSettings) + FFMODPerPlatformConfig XB1PlatformSettings; + + UPROPERTY(config, EditAnywhere, Category = PerPlatformSettings) + FFMODPerPlatformConfig PS4PlatformSettings; + + UPROPERTY(config, EditAnywhere, Category = PerPlatformSettings) + FFMODPerPlatformConfig SwitchPlatformSettings; /** * File buffer size in bytes (2048 by default). diff --git a/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp b/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp index 5e370643..d402ee5a 100755 --- a/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp +++ b/FMODStudio/Source/FMODStudio/Private/FMODAudioComponent.cpp @@ -772,6 +772,19 @@ void UFMODAudioComponent::SetVolume(float Volume) } } +float UFMODAudioComponent::GetVolume() +{ + if (!StudioInstance) return 0.0f; + + float volume = 0.0f; + FMOD_RESULT Result = StudioInstance->getVolume(&volume); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get volume")); + } + return volume; +} + void UFMODAudioComponent::SetPitch(float Pitch) { if (StudioInstance) @@ -784,6 +797,19 @@ void UFMODAudioComponent::SetPitch(float Pitch) } } +float UFMODAudioComponent::GetPitch() +{ + if (!StudioInstance) return 1.0f; + + float pitch = 1.0f; + FMOD_RESULT Result = StudioInstance->getPitch(&pitch); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get pitch")); + } + return pitch; +} + void UFMODAudioComponent::SetPaused(bool Paused) { if (StudioInstance) @@ -796,6 +822,19 @@ void UFMODAudioComponent::SetPaused(bool Paused) } } +bool UFMODAudioComponent::GetPaused() +{ + if (!StudioInstance) return false; + + bool paused = false; + FMOD_RESULT Result = StudioInstance->getPaused(&paused); + if (Result != FMOD_OK) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get paused")); + } + return paused; +} + void UFMODAudioComponent::SetParameter(FName Name, float Value) { if (StudioInstance) diff --git a/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp b/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp index 781d92d6..62c46bcf 100755 --- a/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp +++ b/FMODStudio/Source/FMODStudio/Private/FMODBlueprintStatics.cpp @@ -344,6 +344,29 @@ void UFMODBlueprintStatics::VCASetVolume(class UFMODVCA *Vca, float Volume) } } +float UFMODBlueprintStatics::VCAGetVolume(class UFMODVCA *Vca) +{ + float volume = -1.0f; + + FMOD::Studio::System *StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + if (StudioSystem != nullptr && IsValid(Vca)) + { + FMOD::Studio::ID guid = FMODUtils::ConvertGuid(Vca->AssetGuid); + FMOD::Studio::VCA *vca = nullptr; + FMOD_RESULT result = StudioSystem->getVCAByID(&guid, &vca); + if (result == FMOD_OK && vca != nullptr) + { + vca->getVolume(&volume); + } + } + if (volume < 0.0f) + { + UE_LOG(LogFMOD, Warning, TEXT("Failed to get vca volume")); + volume = 0.0f; + } + return volume; +} + void UFMODBlueprintStatics::SetGlobalParameterByName(FName Name, float Value) { FMOD::Studio::System *StudioSystem = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); @@ -662,7 +685,7 @@ void UFMODBlueprintStatics::MixerResume() } } -void UFMODBlueprintStatics::SetLocale(const FString& Locale) +bool UFMODBlueprintStatics::SetLocale(const FString& Locale) { - IFMODStudioModule::Get().SetLocale(Locale); + return IFMODStudioModule::Get().SetLocale(Locale); } diff --git a/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp b/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp index 4f92d927..1d466b71 100755 --- a/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp +++ b/FMODStudio/Source/FMODStudio/Private/FMODSettings.cpp @@ -19,23 +19,22 @@ UFMODSettings::UFMODSettings(const FObjectInitializer &ObjectInitializer) { MasterBankName = TEXT("Master"); BankOutputDirectory.Path = TEXT("FMOD"); - OutputFormat = EFMODSpeakerMode::Surround_5_1; ContentBrowserPrefix = TEXT("/Game/FMOD/"); bLoadAllBanks = true; bLoadAllSampleData = false; bEnableLiveUpdate = true; bVol0Virtual = true; Vol0VirtualLevel = 0.0001f; + DopplerScale = 1.0f; + DistanceFactor = 1.0f; + RolloffScale = 1.0f; RealChannelCount = 64; TotalChannelCount = 512; - DSPBufferLength = 0; - DSPBufferCount = 0; FileBufferSize = 2048; StudioUpdatePeriod = 0; LiveUpdatePort = 9264; EditorLiveUpdatePort = 9265; ReloadBanksDelay = 5; - bMatchHardwareSampleRate = true; bLockAllBuses = false; } diff --git a/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp b/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp index 3e67a0eb..edafe737 100755 --- a/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp +++ b/FMODStudio/Source/FMODStudio/Private/FMODStudioModule.cpp @@ -26,6 +26,7 @@ #include "Runtime/Media/Public/IMediaClockSink.h" #include "Runtime/Media/Public/IMediaModule.h" #include "TimerManager.h" +#include "Internationalization/Culture.h" #include "fmod_studio.hpp" #include "fmod_errors.h" @@ -194,6 +195,7 @@ class FFMODStudioModule : public IFMODStudioModule void CreateStudioSystem(EFMODSystemContext::Type Type); void DestroyStudioSystem(EFMODSystemContext::Type Type); + virtual void DestroyStudioSystems() override; bool Tick(float DeltaTime); @@ -245,6 +247,19 @@ class FFMODStudioModule : public IFMODStudioModule virtual bool SetLocale(const FString& Locale) override; + virtual FDelegateHandle AddStudioInstanceWatcher(FStudioInstanceWatcherDelegate callback) override; + virtual void RemoveStudioInstanceWatcher(FDelegateHandle callback) override; + virtual FDelegateHandle AddStudioBankLoadWatcher(FStudioBankLoadWatcherDelegate callback) override; + virtual void RemoveStudioBankLoadWatcher(FDelegateHandle callback) override; + virtual FDelegateHandle AddMixerPreSuspendWatcher(FMixerPreSuspendWatcherDelegate callback) override; + virtual void RemoveMixerPreSuspendWatcher(FDelegateHandle callback) override; + virtual FDelegateHandle AddMixerPostSuspendWatcher(FMixerPostSuspendWatcherDelegate callback) override; + virtual void RemoveMixerPostSuspendWatcher(FDelegateHandle callback) override; + virtual FDelegateHandle AddMixerPreResumeWatcher(FMixerPreResumeWatcherDelegate callback) override; + virtual void RemoveMixerPreResumeWatcher(FDelegateHandle callback) override; + virtual FDelegateHandle AddMixerPostResumeWatcher(FMixerPostResumeWatcherDelegate callback) override; + virtual void RemoveMixerPostResumeWatcherDelegate(FDelegateHandle callback) override; + void ResetInterpolation(); #if PLATFORM_IOS @@ -314,6 +329,16 @@ class FFMODStudioModule : public IFMODStudioModule void *MemPool; bool bLoadAllSampleData; + + /** Delegates that want to be informed when studio instances are created or destroyed */ + FStudioInstanceWatchers instanceWatchers; + /** Delegates that want to be informed when banks are loaded and unloaded */ + FStudioBankLoadWatchers bankLoadWatchers; + /** Delegates that want to be informed when suspending or resuming */ + FMixerPreSuspendWatchers preSuspendWatchers; + FMixerPostSuspendWatchers postSuspendWatchers; + FMixerPreResumeWatchers preResumeWatchers; + FMixerPostResumeWatchers postResumeWatchers; }; IMPLEMENT_MODULE(FFMODStudioModule, FMODStudio) @@ -540,7 +565,6 @@ void FFMODStudioModule::CreateStudioSystem(EFMODSystemContext::Type Type) const UFMODSettings &Settings = *GetDefault(); bLoadAllSampleData = Settings.bLoadAllSampleData; - FMOD_SPEAKERMODE OutputMode = ConvertSpeakerMode(Settings.OutputFormat); FMOD_STUDIO_INITFLAGS StudioInitFlags = FMOD_STUDIO_INIT_NORMAL; FMOD_INITFLAGS InitFlags = FMOD_INIT_NORMAL; @@ -591,8 +615,18 @@ void FFMODStudioModule::CreateStudioSystem(EFMODSystemContext::Type Type) InitData = (void *)WavWriterDestUTF8.Get(); } - int SampleRate = Settings.SampleRate; - if (Settings.bMatchHardwareSampleRate) +#if PLATFORM_PS4 + const FFMODPerPlatformConfig& platformConfig = Settings.PS4PlatformSettings; +#elif PLATFORM_XBOXONE + const FFMODPerPlatformConfig& platformConfig = Settings.XB1PlatformSettings; +#elif PLATFORM_SWITCH + const FFMODPerPlatformConfig& platformConfig = Settings.SwitchPlatformSettings; +#else + const FFMODPerPlatformConfig& platformConfig = Settings.DefaultPlatformSettings; +#endif + + int SampleRate = platformConfig.SampleRate; + if (platformConfig.bMatchHardwareSampleRate) { int DefaultSampleRate = 0; verifyfmod(lowLevelSystem->getSoftwareFormat(&DefaultSampleRate, 0, 0)); @@ -607,13 +641,13 @@ void FFMODStudioModule::CreateStudioSystem(EFMODSystemContext::Type Type) } } - verifyfmod(lowLevelSystem->setSoftwareFormat(SampleRate, OutputMode, 0)); + verifyfmod(lowLevelSystem->setSoftwareFormat(SampleRate, ConvertSpeakerMode(platformConfig.OutputFormat), 0)); verifyfmod(lowLevelSystem->setSoftwareChannels(Settings.RealChannelCount)); AttachFMODFileSystem(lowLevelSystem, Settings.FileBufferSize); - if (Settings.DSPBufferLength > 0 && Settings.DSPBufferCount > 0) + if (platformConfig.DSPBufferLength > 0 && platformConfig.DSPBufferCount > 0) { - verifyfmod(lowLevelSystem->setDSPBufferSize(Settings.DSPBufferLength, Settings.DSPBufferCount)); + verifyfmod(lowLevelSystem->setDSPBufferSize(platformConfig.DSPBufferLength, platformConfig.DSPBufferCount)); } FMOD_ADVANCEDSETTINGS advSettings = { 0 }; @@ -684,22 +718,30 @@ void FFMODStudioModule::CreateStudioSystem(EFMODSystemContext::Type Type) MediaModule->GetClock().AddSink(ClockSinks[Type].ToSharedRef()); } + + verifyfmod(lowLevelSystem->set3DSettings(Settings.DopplerScale, Settings.DistanceFactor, Settings.RolloffScale)); + + instanceWatchers.Broadcast(Type, StudioSystem[Type]); } void FFMODStudioModule::DestroyStudioSystem(EFMODSystemContext::Type Type) { UE_LOG(LogFMOD, Verbose, TEXT("DestroyStudioSystem for context %s"), FMODSystemContextNames[Type]); + instanceWatchers.Broadcast(Type, nullptr); + if (ClockSinks[Type].IsValid()) { // Calling through the shared ptr enforces thread safety with the media clock ClockSinks[Type]->OnDestroyStudioSystem(); - IMediaModule *MediaModule = FModuleManager::LoadModulePtr("Media"); - - if (MediaModule != nullptr) + if (!bIsShuttingDown) { - MediaModule->GetClock().RemoveSink(ClockSinks[Type].ToSharedRef()); + IMediaModule *MediaModule = FModuleManager::LoadModulePtr("Media"); + if (MediaModule != nullptr) + { + MediaModule->GetClock().RemoveSink(ClockSinks[Type].ToSharedRef()); + } } ClockSinks[Type].Reset(); @@ -763,6 +805,13 @@ void FFMODStudioModule::DestroyStudioSystem(EFMODSystemContext::Type Type) } } +void FFMODStudioModule::DestroyStudioSystems() +{ + DestroyStudioSystem(EFMODSystemContext::Auditioning); + DestroyStudioSystem(EFMODSystemContext::Runtime); + DestroyStudioSystem(EFMODSystemContext::Editor); +} + bool FFMODStudioModule::Tick(float DeltaTime) { if (ClockSinks[EFMODSystemContext::Auditioning].IsValid()) @@ -1169,8 +1218,13 @@ void FFMODStudioModule::SetSystemPaused(bool paused) // Resume mixer before making calls for Android in particular if (!paused) { + preResumeWatchers.Broadcast(); LowLevelSystem->mixerResume(); } + else + { + preSuspendWatchers.Broadcast(); + } FMOD::ChannelGroup *MasterChannelGroup = nullptr; verifyfmod(LowLevelSystem->getMasterChannelGroup(&MasterChannelGroup)); @@ -1179,6 +1233,11 @@ void FFMODStudioModule::SetSystemPaused(bool paused) if (paused) { LowLevelSystem->mixerSuspend(); + postSuspendWatchers.Broadcast(); + } + else + { + postResumeWatchers.Broadcast(); } } @@ -1189,6 +1248,7 @@ void FFMODStudioModule::SetSystemPaused(bool paused) void FFMODStudioModule::ShutdownModule() { UE_LOG(LogFMOD, Verbose, TEXT("FFMODStudioModule shutdown")); + bIsShuttingDown = true; DestroyStudioSystem(EFMODSystemContext::Auditioning); DestroyStudioSystem(EFMODSystemContext::Runtime); @@ -1262,10 +1322,94 @@ bool FFMODStudioModule::SetLocale(const FString& LocaleName) return false; } +FDelegateHandle FFMODStudioModule::AddStudioInstanceWatcher(FStudioInstanceWatcherDelegate callback) +{ + // let new watcher know about the current state... + for (int i = 0; i < EFMODSystemContext::Max; ++i) + callback.ExecuteIfBound((EFMODSystemContext::Type)i, StudioSystem[i]); + return instanceWatchers.Add(callback); +} + +FDelegateHandle FFMODStudioModule::AddStudioBankLoadWatcher(FStudioBankLoadWatcherDelegate callback) +{ + // Tell the caller about banks that have already been loaded... + for (int t = EFMODSystemContext::Auditioning; t < EFMODSystemContext::Max; ++t) + { + FMOD::Studio::System* system = GetStudioSystem((EFMODSystemContext::Type)t); + if (!system) continue; + + int numBanks; + verifyfmod(system->getBankCount(&numBanks)); + if (numBanks == 0) continue; + + FMOD::Studio::Bank** banks = new FMOD::Studio::Bank*[numBanks]; + verifyfmod(system->getBankList(banks, numBanks, &numBanks)); + for (int i = 0; i < numBanks; ++i) + { + callback.ExecuteIfBound((EFMODSystemContext::Type)t, system, banks[i]); + } + } + return bankLoadWatchers.Add(callback); +} + +FDelegateHandle FFMODStudioModule::AddMixerPreSuspendWatcher(FMixerPreSuspendWatcherDelegate callback) +{ + return preSuspendWatchers.Add(callback); +} + +FDelegateHandle FFMODStudioModule::AddMixerPostSuspendWatcher(FMixerPostSuspendWatcherDelegate callback) +{ + return postSuspendWatchers.Add(callback); +} + +FDelegateHandle FFMODStudioModule::AddMixerPreResumeWatcher(FMixerPreResumeWatcherDelegate callback) +{ + return preResumeWatchers.Add(callback); +} + +FDelegateHandle FFMODStudioModule::AddMixerPostResumeWatcher(FMixerPostResumeWatcherDelegate callback) +{ + return postResumeWatchers.Add(callback); +} + +void FFMODStudioModule::RemoveStudioInstanceWatcher(FDelegateHandle callback) +{ + instanceWatchers.Remove(callback); +} + + +void FFMODStudioModule::RemoveStudioBankLoadWatcher(FDelegateHandle callback) +{ + bankLoadWatchers.Remove(callback); +} + +void FFMODStudioModule::RemoveMixerPreSuspendWatcher(FDelegateHandle callback) +{ + preSuspendWatchers.Remove(callback); +} + +void FFMODStudioModule::RemoveMixerPostSuspendWatcher(FDelegateHandle callback) +{ + postSuspendWatchers.Remove(callback); +} + +void FFMODStudioModule::RemoveMixerPreResumeWatcher(FDelegateHandle callback) +{ + preResumeWatchers.Remove(callback); +} + +void FFMODStudioModule::RemoveMixerPostResumeWatcherDelegate(FDelegateHandle callback) +{ + postResumeWatchers.Remove(callback); +} + void FFMODStudioModule::LoadBanks(EFMODSystemContext::Type Type) { const UFMODSettings &Settings = *GetDefault(); + FCultureRef culture = FInternationalization::Get().GetCurrentLanguage(); + SetLocale(culture.Get().GetTwoLetterISOLanguageName()); + FailedBankLoads[Type].Reset(); if (Type == EFMODSystemContext::Auditioning || Type == EFMODSystemContext::Editor) { @@ -1351,6 +1495,10 @@ void FFMODStudioModule::LoadBanks(EFMODSystemContext::Type Type) // Optionally lock all buses to make sure they are created if (MasterBank && Settings.bLockAllBuses) { + // The attempt to lock all buses will fail if the bank load + // hasn't been processed, so wait... + StudioSystem[Type]->flushCommands(); + UE_LOG(LogFMOD, Verbose, TEXT("Locking all buses")); int BusCount = 0; verifyfmod(MasterBank->getBusCount(&BusCount)); @@ -1386,6 +1534,8 @@ void FFMODStudioModule::LoadBanks(EFMODSystemContext::Type Type) { verifyfmod(Entry.Bank->loadSampleData()); } + + bankLoadWatchers.Broadcast(Type, StudioSystem[Type], Entry.Bank); } if (Entry.Bank == nullptr || Entry.Result != FMOD_OK) { diff --git a/FMODStudio/Source/FMODStudio/Private/Sequencer/FMODEventControlSectionTemplate.cpp b/FMODStudio/Source/FMODStudio/Private/Sequencer/FMODEventControlSectionTemplate.cpp index 2eff969e..b4ae033f 100644 --- a/FMODStudio/Source/FMODStudio/Private/Sequencer/FMODEventControlSectionTemplate.cpp +++ b/FMODStudio/Source/FMODStudio/Private/Sequencer/FMODEventControlSectionTemplate.cpp @@ -56,11 +56,15 @@ struct FFMODEventKeyState : IPersistentEvaluationData FKeyHandle InvalidKeyHandle; }; +static FSharedPersistentDataKey FmodEventStateSharedKey(FMovieSceneSharedDataId::Allocate(), FMovieSceneEvaluationOperand()); +struct EventControlState : IPersistentEvaluationData { bool playing = false; }; + struct FFMODEventControlExecutionToken : IMovieSceneExecutionToken { - FFMODEventControlExecutionToken(EFMODEventControlKey InEventControlKey, FFrameTime InKeyTime) + FFMODEventControlExecutionToken(EFMODEventControlKey InEventControlKey, FFrameTime InKeyTime, bool InStartedOnTime) : EventControlKey(InEventControlKey) , KeyTime(InKeyTime) + , StartedOnTime(InStartedOnTime) { } @@ -70,6 +74,7 @@ struct FFMODEventControlExecutionToken : IMovieSceneExecutionToken { for (TWeakObjectPtr<> &WeakObject : Player.FindBoundObjects(Operand)) { + EventControlState& eventControlSate = PersistentData.GetOrAdd(FmodEventStateSharedKey); UFMODAudioComponent *AudioComponent = Cast(WeakObject.Get()); if (!AudioComponent) @@ -92,10 +97,19 @@ struct FFMODEventControlExecutionToken : IMovieSceneExecutionToken EFMODSystemContext::Type SystemContext = (GWorld && GWorld->WorldType == EWorldType::Editor) ? EFMODSystemContext::Editor : EFMODSystemContext::Runtime; AudioComponent->PlayInternal(SystemContext); + AudioComponent->SetPitch(Player.GetPlaybackSpeed()); + if (!StartedOnTime) + { + FFrameTime currentPos = Context.GetOffsetTime(-KeyTime); + int32 timelineMs = (int32)(Context.GetFrameRate().AsSeconds(currentPos) * 1000.0); + AudioComponent->SetTimelinePosition(timelineMs); + } + eventControlSate.playing = true; } else if (EventControlKey == EFMODEventControlKey::Stop) { AudioComponent->Stop(); + eventControlSate.playing = false; } } } @@ -103,6 +117,7 @@ struct FFMODEventControlExecutionToken : IMovieSceneExecutionToken EFMODEventControlKey EventControlKey; FFrameTime KeyTime; + bool StartedOnTime; }; FFMODEventControlSectionTemplate::FFMODEventControlSectionTemplate(const UFMODEventControlSection &Section) @@ -118,10 +133,11 @@ void FFMODEventControlSectionTemplate::Evaluate(const FMovieSceneEvaluationOpera if (!bPlaying) { - ExecutionTokens.Add(FFMODEventControlExecutionToken(EFMODEventControlKey::Stop, FFrameTime(0))); + ExecutionTokens.Add(FFMODEventControlExecutionToken(EFMODEventControlKey::Stop, FFrameTime(0), true)); } else { + const EventControlState* eventControlSate = PersistentData.Find(FmodEventStateSharedKey); TRange PlaybackRange = Context.GetFrameNumberRange(); TMovieSceneChannelData ChannelData = ControlKeys.GetData(); @@ -130,9 +146,10 @@ void FFMODEventControlSectionTemplate::Evaluate(const FMovieSceneEvaluationOpera TArrayView Values = ChannelData.GetValues(); const int32 LastKeyIndex = Algo::UpperBound(Times, PlaybackRange.GetUpperBoundValue()) - 1; - if (LastKeyIndex >= 0 && PlaybackRange.Contains(Times[LastKeyIndex])) + bool startedOnTime = LastKeyIndex >= 0 && PlaybackRange.Contains(Times[LastKeyIndex]); + if (startedOnTime || ((EFMODEventControlKey)Values[LastKeyIndex] == EFMODEventControlKey::Play && eventControlSate && !eventControlSate->playing)) { - FFMODEventControlExecutionToken NewToken((EFMODEventControlKey)Values[LastKeyIndex], Times[LastKeyIndex]); + FFMODEventControlExecutionToken NewToken((EFMODEventControlKey)Values[LastKeyIndex], Times[LastKeyIndex], startedOnTime); ExecutionTokens.Add(MoveTemp(NewToken)); } } diff --git a/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h b/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h index 2ff9a5d9..f4879bc8 100755 --- a/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h +++ b/FMODStudio/Source/FMODStudio/Public/FMODStudioModule.h @@ -11,6 +11,7 @@ namespace Studio class System; class EventDescription; class EventInstance; +class Bank; } } @@ -41,6 +42,20 @@ enum Type }; } +DECLARE_MULTICAST_DELEGATE_TwoParams(FStudioInstanceWatchers, EFMODSystemContext::Type, FMOD::Studio::System*) +typedef FStudioInstanceWatchers::FDelegate FStudioInstanceWatcherDelegate; +DECLARE_MULTICAST_DELEGATE_ThreeParams(FStudioBankLoadWatchers, EFMODSystemContext::Type, FMOD::Studio::System*, FMOD::Studio::Bank*) +typedef FStudioBankLoadWatchers::FDelegate FStudioBankLoadWatcherDelegate; + +DECLARE_MULTICAST_DELEGATE(FMixerPreSuspendWatchers) +typedef FMixerPreSuspendWatchers::FDelegate FMixerPreSuspendWatcherDelegate; +DECLARE_MULTICAST_DELEGATE(FMixerPostSuspendWatchers) +typedef FMixerPostSuspendWatchers::FDelegate FMixerPostSuspendWatcherDelegate; +DECLARE_MULTICAST_DELEGATE(FMixerPreResumeWatchers) +typedef FMixerPreResumeWatchers::FDelegate FMixerPreResumeWatcherDelegate; +DECLARE_MULTICAST_DELEGATE(FMixerPostResumeWatchers) +typedef FMixerPostResumeWatchers::FDelegate FMixerPostResumeWatcherDelegate; + /** * The public interface to this module */ @@ -167,4 +182,26 @@ class IFMODStudioModule : public IModuleInterface /** Called by the editor module when banks have been modified on disk */ virtual void ReloadBanks() = 0; #endif + + /** Support for delegates to be called when studio instances are created and destroyed */ + virtual FDelegateHandle AddStudioInstanceWatcher(FStudioInstanceWatcherDelegate callback) = 0; + virtual void RemoveStudioInstanceWatcher(FDelegateHandle callback) = 0; + + /** Support for delegates to be called when banks are loaded and unloaded */ + virtual FDelegateHandle AddStudioBankLoadWatcher(FStudioBankLoadWatcherDelegate callback) = 0; + virtual void RemoveStudioBankLoadWatcher(FDelegateHandle callback) = 0; + + /** Support for delegates to be called when platform suspend/resume callbacks will be handled */ + virtual FDelegateHandle AddMixerPreSuspendWatcher(FMixerPreSuspendWatcherDelegate callback) = 0; + virtual void RemoveMixerPreSuspendWatcher(FDelegateHandle callback) = 0; + virtual FDelegateHandle AddMixerPostSuspendWatcher(FMixerPostSuspendWatcherDelegate callback) = 0; + virtual void RemoveMixerPostSuspendWatcher(FDelegateHandle callback) = 0; + virtual FDelegateHandle AddMixerPreResumeWatcher(FMixerPreResumeWatcherDelegate callback) = 0; + virtual void RemoveMixerPreResumeWatcher(FDelegateHandle callback) = 0; + virtual FDelegateHandle AddMixerPostResumeWatcher(FMixerPostResumeWatcherDelegate callback) = 0; + virtual void RemoveMixerPostResumeWatcherDelegate(FDelegateHandle callback) = 0; + + bool bIsShuttingDown = false; + + virtual void DestroyStudioSystems() = 0; }; diff --git a/FMODStudio/Source/FMODStudio/Public/FMODUtils.h b/FMODStudio/Source/FMODStudio/Public/FMODUtils.h index f5035107..42aa4b10 100755 --- a/FMODStudio/Source/FMODStudio/Public/FMODUtils.h +++ b/FMODStudio/Source/FMODStudio/Public/FMODUtils.h @@ -9,6 +9,7 @@ #include "Engine/Engine.h" #include "FMODStudioModule.h" +#include "FMODAsset.h" #define verifyfmod(fn) \ { \ @@ -177,6 +178,12 @@ inline FString LookupNameFromGuid(FMOD::Studio::System *StudioSystem, const FGui return LookupNameFromGuid(StudioSystem, ConvertGuid(Guid)); } +inline FString GetAssetPath(UFMODAsset* asset) +{ + FMOD::Studio::System *system = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime); + return LookupNameFromGuid(system, asset->AssetGuid); +} + inline FString ParameterTypeToString(FMOD_STUDIO_PARAMETER_TYPE Type) { switch (Type) diff --git a/FMODStudio/Source/FMODStudioEditor/Private/FMODBankUpdateNotifier.cpp b/FMODStudio/Source/FMODStudioEditor/Private/FMODBankUpdateNotifier.cpp index ce5afab0..7623fcd8 100644 --- a/FMODStudio/Source/FMODStudioEditor/Private/FMODBankUpdateNotifier.cpp +++ b/FMODStudio/Source/FMODStudioEditor/Private/FMODBankUpdateNotifier.cpp @@ -63,7 +63,7 @@ void FFMODBankUpdateNotifier::Refresh() { if (!FilePath.IsEmpty()) { - const FDateTime NewFileTime = IFileManager::Get().GetTimeStamp(*FilePath); + const FDateTime NewFileTime = MostRecentFileTime(); if (NewFileTime != FileTime) { const UFMODSettings &Settings = *GetDefault();