// Copyright Epic Games, Inc. All Rights Reserved. #include "SoundModulationDestinationLayout.h" #include "AudioDevice.h" #include "AudioDeviceManager.h" #include "AudioEditorModule.h" #include "Delegates/Delegate.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Styling/AppStyle.h" #include "IAudioModulation.h" #include "Logging/LogMacros.h" #include "Misc/Attribute.h" #include "Sound/SoundModulationDestination.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "SoundModulationParameter" namespace ModDestinationLayoutUtils { IAudioModulationManager* GetEditorModulationManager() { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { FAudioDeviceHandle AudioDeviceHandle = World->GetAudioDevice(); if (AudioDeviceHandle.IsValid() && AudioDeviceHandle->IsModulationPluginEnabled()) { return AudioDeviceHandle->ModulationInterface.Get(); } } } return nullptr; } bool IsModulationEnabled() { return GetEditorModulationManager() != nullptr; } FName GetParameterNameFromMetaData(const TSharedRef& InHandle) { static const FName AudioParamFieldName("AudioParam"); if (InHandle->HasMetaData(AudioParamFieldName)) { FString ParamString = InHandle->GetMetaData(AudioParamFieldName); return FName(ParamString); } return FName(); } bool IsParamMismatched(TSharedRef ModulatorHandle, TSharedRef StructPropertyHandle, FName* OutModParamName = nullptr, FName* OutDestParamName = nullptr) { if (OutModParamName) { *OutModParamName = FName(); } if (OutDestParamName) { *OutDestParamName = FName(); } UObject* ModObject = nullptr; ModulatorHandle->GetValue(ModObject); USoundModulatorBase* ModBase = Cast(ModObject); if (!ModBase) { return false; } const FName ModParamName = ModBase->GetOutputParameter().ParameterName; const FName DestParamName = ModDestinationLayoutUtils::GetParameterNameFromMetaData(StructPropertyHandle); if (ModParamName != FName() && DestParamName != FName() && ModParamName != DestParamName) { if (OutModParamName) { *OutModParamName = ModParamName; } if (OutDestParamName) { *OutDestParamName = DestParamName; } return true; } return false; } FText SetMetaData(TSharedRef StructPropertyHandle, TSharedRef ValueHandle, FText& OutUnitDisplayText, FName& OutParamName) { bool bClampValuesSet = false; float ClampMinValue = 0.0f; float ClampMaxValue = 1.0f; float UIMinValue = 0.0f; float UIMaxValue = 1.0f; if (StructPropertyHandle->HasMetaData("ClampMin")) { bClampValuesSet = true; FString ParamString = StructPropertyHandle->GetMetaData("ClampMin"); ClampMinValue = FCString::Atof(*ParamString); } if (StructPropertyHandle->HasMetaData("ClampMax")) { FString ParamString = StructPropertyHandle->GetMetaData("ClampMax"); ClampMaxValue = FCString::Atof(*ParamString); bClampValuesSet = true; } OutParamName = ModDestinationLayoutUtils::GetParameterNameFromMetaData(StructPropertyHandle); if (OutParamName != FName()) { // If parameter was provided, it overrides ClampMin/Max. const Audio::FModulationParameter& Parameter = Audio::GetModulationParameter(OutParamName); // if no valid parameter was found & the user has specified their own clamping, don't override it with the default parameter range of [0,1] if (false == (Parameter.ParameterName == FName() && bClampValuesSet)) { UIMinValue = Parameter.MinValue; UIMaxValue = Parameter.MaxValue; ClampMinValue = UIMinValue; ClampMaxValue = UIMaxValue; OutUnitDisplayText = Parameter.UnitDisplayName; if (bClampValuesSet) { UE_LOG(LogAudioEditor, Verbose, TEXT("ClampMin/Max overridden by AudioModulation plugin asset with ParamName '%s'."), *OutParamName.ToString()); } } } // User data overrides UIMin/Max if its in clamp range. if (StructPropertyHandle->HasMetaData("UIMin")) { float NewMin = UIMinValue; FString ParamString = StructPropertyHandle->GetMetaData("UIMin"); NewMin = FCString::Atof(*ParamString); UIMinValue = FMath::Clamp(NewMin, ClampMinValue, ClampMaxValue); } if (StructPropertyHandle->HasMetaData("UIMax")) { float NewMax = UIMaxValue; FString ParamString = StructPropertyHandle->GetMetaData("UIMax"); NewMax = FCString::Atof(*ParamString); UIMaxValue = FMath::Clamp(NewMax, ClampMinValue, ClampMaxValue); } ValueHandle->SetInstanceMetaData("ClampMin", FString::Printf(TEXT("%f"), ClampMinValue)); ValueHandle->SetInstanceMetaData("ClampMax", FString::Printf(TEXT("%f"), ClampMaxValue)); ValueHandle->SetInstanceMetaData("UIMin", FString::Printf(TEXT("%f"), UIMinValue)); ValueHandle->SetInstanceMetaData("UIMax", FString::Printf(TEXT("%f"), UIMaxValue)); return OutUnitDisplayText; } void CustomizeChildren_AddValueRow( IDetailChildrenBuilder& ChildBuilder, TSharedRef StructPropertyHandle, TSharedRef ValueHandle, TSharedRef ModulatorHandle, TSharedRef EnablementHandle) { FText UnitDisplayText = FText::GetEmpty(); FName ParamName; SetMetaData(StructPropertyHandle, ValueHandle, UnitDisplayText, ParamName); const FText DisplayName = StructPropertyHandle->GetPropertyDisplayName(); ChildBuilder.AddCustomRow(DisplayName) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(DisplayName) .ToolTipText(StructPropertyHandle->GetToolTipText()) ] .ValueContent() .MinDesiredWidth(250.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ ValueHandle->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(UnitDisplayText) .ToolTipText(ValueHandle->GetToolTipText()) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ EnablementHandle->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ EnablementHandle->CreatePropertyNameWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SButton) .ToolTipText(LOCTEXT("ResetToParameterDefaultToolTip", "Reset to parameter's default")) .ButtonStyle(FAppStyle::Get(), TEXT("NoBorder")) .ContentPadding(0.0f) .Visibility(TAttribute::Create([ParamName, ValueHandle] { float CurrentValue = 0.0f; ValueHandle->GetValue(CurrentValue); const Audio::FModulationParameter& Parameter = Audio::GetModulationParameter(ParamName); return Parameter.DefaultValue == CurrentValue ? EVisibility::Hidden : EVisibility::Visible; })) .OnClicked(FOnClicked::CreateLambda([ParamName, ValueHandle]() { Audio::FModulationParameter Parameter = Audio::GetModulationParameter(ParamName); ValueHandle->SetValue(Parameter.DefaultValue); return FReply::Handled(); })) .Content() [ SNew(SImage) .Image(FAppStyle::GetBrush("PropertyWindow.DiffersFromDefault")) ] ] ]; EnablementHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([EnablementHandle, ValueHandle, StructPropertyHandle, ModulatorHandle]() { bool bEnabled = false; EnablementHandle->GetValue(bEnabled); if (bEnabled) { const FName ParamName = ModDestinationLayoutUtils::GetParameterNameFromMetaData(StructPropertyHandle); const Audio::FModulationParameter& Parameter = Audio::GetModulationParameter(ParamName); ValueHandle->SetValue(Parameter.DefaultValue); } else { UObject* NullObj = nullptr; ModulatorHandle->SetValue(NullObj); } })); } void CustomizeChildren_AddValueNoModRow(IDetailChildrenBuilder& ChildBuilder, TSharedRef StructPropertyHandle, TSharedRef ValueHandle) { FText UnitDisplayText = FText::GetEmpty(); FName ParamName; SetMetaData(StructPropertyHandle, ValueHandle, UnitDisplayText, ParamName); const FText DisplayName = StructPropertyHandle->GetPropertyDisplayName(); FDetailWidgetRow& ValueNoModRow = ChildBuilder.AddCustomRow(DisplayName); ValueNoModRow.NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(DisplayName) .ToolTipText(StructPropertyHandle->GetToolTipText()) ] .ValueContent() .MinDesiredWidth(120.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ ValueHandle->CreatePropertyValueWidget() ] ]; } void CustomizeChildren_AddModulatorRow(IDetailChildrenBuilder& ChildBuilder, TSharedRef StructPropertyHandle, TSharedRef ModulatorHandle, TSharedRef EnablementHandle) { const FText DisplayName = StructPropertyHandle->GetPropertyDisplayName(); ChildBuilder.AddCustomRow(DisplayName) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(FText::Format(LOCTEXT("SoundModulationParameter_ModulatorFormat", "{0} Modulator"), DisplayName)) .ToolTipText(ModulatorHandle->GetToolTipText()) ] .ValueContent() .MinDesiredWidth(200.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ ModulatorHandle->CreatePropertyValueWidget() ] ] .Visibility(TAttribute::Create([EnablementHandle]() { bool bModulationEnabled = false; EnablementHandle->GetValue(bModulationEnabled); return bModulationEnabled ? EVisibility::Visible : EVisibility::Hidden; })); ChildBuilder.AddCustomRow(LOCTEXT("SoundModulationDestinationLayout_UnitMismatchHeadingWarning", "Unit Mismatch Warning")) .ValueContent() .MinDesiredWidth(150.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(10.0f, 0.0f, 0.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFontBold()) .Text(TAttribute::Create([ModulatorHandle, StructPropertyHandle]() { UObject* ModObject = nullptr; ModulatorHandle->GetValue(ModObject); UObject* DestObject = nullptr; FName ModName; FName DestName; if (ModDestinationLayoutUtils::IsParamMismatched(ModulatorHandle, StructPropertyHandle, &ModName, &DestName)) { return FText::Format(LOCTEXT("ModulationDestinationLayout_UnitMismatchFormat", "Parameter Mismatch: Modulator Output = {0}, Destination Input = {1}"), FText::FromName(ModName), FText::FromName(DestName)); } return FText::GetEmpty(); })) ] ] .Visibility(TAttribute::Create([ModulatorHandle, StructPropertyHandle]() { return ModDestinationLayoutUtils::IsParamMismatched(ModulatorHandle, StructPropertyHandle) ? EVisibility::Visible : EVisibility::Hidden; })); } void SetBoundsMetaData(FName FieldName, float InDefault, const TSharedRef& InHandle, TSharedRef& OutHandle) { if (InHandle->HasMetaData(FieldName)) { const FString& Value = InHandle->GetMetaData(FieldName); OutHandle->SetInstanceMetaData(FieldName, Value); } else { OutHandle->SetInstanceMetaData(FieldName, FString::Printf(TEXT("%f"), InDefault)); } } } // namespace ModParamLayoutUtils void FSoundModulationDestinationLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } void FSoundModulationDestinationLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { TMap> PropertyHandles; uint32 NumChildren; StructPropertyHandle->GetNumChildren(NumChildren); for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); const FName PropertyName = ChildHandle->GetProperty()->GetFName(); PropertyHandles.Add(PropertyName, ChildHandle); } TSharedRefEnablementHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDestinationSettings, bEnableModulation)).ToSharedRef(); TSharedRefModulatorHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDestinationSettings, Modulator)).ToSharedRef(); TSharedRefValueHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDestinationSettings, Value)).ToSharedRef(); if (ModDestinationLayoutUtils::IsModulationEnabled()) { ModDestinationLayoutUtils::CustomizeChildren_AddValueRow(ChildBuilder, StructPropertyHandle, ValueHandle, ModulatorHandle, EnablementHandle); ModDestinationLayoutUtils::CustomizeChildren_AddModulatorRow(ChildBuilder, StructPropertyHandle, ModulatorHandle, EnablementHandle); } else { ModDestinationLayoutUtils::CustomizeChildren_AddValueNoModRow(ChildBuilder, StructPropertyHandle, ValueHandle); } } void FSoundModulationDefaultSettingsLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } void FSoundModulationDefaultSettingsLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { if (ModDestinationLayoutUtils::IsModulationEnabled()) { TMap> PropertyHandles; uint32 NumChildren; StructPropertyHandle->GetNumChildren(NumChildren); for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); const FName PropertyName = ChildHandle->GetProperty()->GetFName(); PropertyHandles.Add(PropertyName, ChildHandle); } TSharedRef VolumeHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultSettings, VolumeModulationDestination)).ToSharedRef(); TSharedRef PitchHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultSettings, PitchModulationDestination)).ToSharedRef(); TSharedRef HighpassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultSettings, HighpassModulationDestination)).ToSharedRef(); TSharedRef LowpassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultSettings, LowpassModulationDestination)).ToSharedRef(); ChildBuilder.AddProperty(VolumeHandle); ChildBuilder.AddProperty(PitchHandle); ChildBuilder.AddProperty(HighpassHandle); ChildBuilder.AddProperty(LowpassHandle); } } void FSoundModulationDefaultRoutingSettingsLayoutCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { } void FSoundModulationDefaultRoutingSettingsLayoutCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { if (ModDestinationLayoutUtils::IsModulationEnabled()) { TMap> PropertyHandles; uint32 NumChildren; StructPropertyHandle->GetNumChildren(NumChildren); for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex) { TSharedRef ChildHandle = StructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef(); const FName PropertyName = ChildHandle->GetProperty()->GetFName(); PropertyHandles.Add(PropertyName, ChildHandle); } TSharedRef VolumeRouting = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, VolumeRouting)).ToSharedRef(); TSharedRef VolumeHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, VolumeModulationDestination)).ToSharedRef(); TSharedRef PitchRouting = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, PitchRouting)).ToSharedRef(); TSharedRef PitchHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, PitchModulationDestination)).ToSharedRef(); TSharedRef HighpassRouting = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, HighpassRouting)).ToSharedRef(); TSharedRef HighpassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, HighpassModulationDestination)).ToSharedRef(); TSharedRef LowpassRouting = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, LowpassRouting)).ToSharedRef(); TSharedRef LowpassHandle = PropertyHandles.FindChecked(GET_MEMBER_NAME_CHECKED(FSoundModulationDefaultRoutingSettings, LowpassModulationDestination)).ToSharedRef(); auto ShowModSettings = [] (TSharedRef RoutingHandle) { return TAttribute::Create([RoutingHandle]() { uint8 RoutingValue = 0; if (RoutingHandle->GetValue(RoutingValue) != FPropertyAccess::Success) { return EVisibility::Hidden; } if (static_cast(RoutingValue) != EModulationRouting::Override) { return EVisibility::Hidden; } return EVisibility::Visible; }); }; ChildBuilder.AddProperty(VolumeRouting); ChildBuilder.AddProperty(VolumeHandle).Visibility(ShowModSettings(VolumeRouting)); ChildBuilder.AddProperty(PitchRouting); ChildBuilder.AddProperty(PitchHandle).Visibility(ShowModSettings(PitchRouting)); ChildBuilder.AddProperty(HighpassRouting); ChildBuilder.AddProperty(HighpassHandle).Visibility(ShowModSettings(HighpassRouting)); ChildBuilder.AddProperty(LowpassRouting); ChildBuilder.AddProperty(LowpassHandle).Visibility(ShowModSettings(LowpassRouting)); } } #undef LOCTEXT_NAMESPACE