Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Private/Sections/MovieSceneAudioSection.cpp
2025-05-18 13:04:45 +08:00

653 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Sections/MovieSceneAudioSection.h"
#include "Decorations/MovieSceneLanguagePreviewDecoration.h"
#include "Decorations/MovieSceneSectionAnchorsDecoration.h"
#include "Tracks/MovieSceneAudioTrack.h"
#include "Sound/SoundBase.h"
#include "UObject/SequencerObjectVersion.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "GameFramework/Actor.h"
#include "MovieScene.h"
#include "MovieSceneCommonHelpers.h"
#include "Misc/FrameRate.h"
#include "Misc/GeneratedTypeName.h"
#include "Misc/PackageName.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "MovieSceneTracksComponentTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneAudioSection)
#if WITH_EDITOR
struct FAudioChannelEditorData
{
FAudioChannelEditorData()
{
Data[0].SetIdentifiers("Volume", NSLOCTEXT("MovieSceneAudioSection", "SoundVolumeText", "Volume"));
Data[1].SetIdentifiers("Pitch", NSLOCTEXT("MovieSceneAudioSection", "PitchText", "Pitch"));
Data[2].SetIdentifiers("AttachActor", NSLOCTEXT("MovieSceneAudioSection", "AttachActorText", "Attach"));
}
FMovieSceneChannelMetaData Data[3];
};
#endif // WITH_EDITOR
namespace
{
float AudioDeprecatedMagicNumber = TNumericLimits<float>::Lowest();
FFrameNumber GetStartOffsetAtTrimTime(FQualifiedFrameTime TrimTime, FFrameNumber StartOffset, FFrameNumber StartFrame)
{
return StartOffset + TrimTime.Time.FrameNumber - StartFrame;
}
}
namespace UE::MovieScene
{
/*
* Entity IDs are an encoded type and index, with the upper bit being the type (scalar iputs vs audio trigger),
* and the lower 31 bits as the entity index
*/
enum class EAudioSectionEntityType : uint8 { MainEntity, InputsEntity, TriggerEntity };
uint32 EncodeEntityID(int32 InIndex, EAudioSectionEntityType InEntityType)
{
check(InIndex >= 0 && InIndex < int32(0x00FFFFFF));
return static_cast<uint32>(InIndex) | ((uint8)InEntityType << 24);
}
void DecodeEntityID(uint32 InEntityID, int32& OutIndex, EAudioSectionEntityType& OutEntityType)
{
OutIndex = static_cast<int32>(InEntityID & 0x00FFFFFF);
OutEntityType = (EAudioSectionEntityType)(InEntityID >> 24);
}
}
UMovieSceneAudioSection::UMovieSceneAudioSection( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
{
Sound = nullptr;
StartOffset_DEPRECATED = AudioDeprecatedMagicNumber;
AudioStartTime_DEPRECATED = AudioDeprecatedMagicNumber;
AudioDilationFactor_DEPRECATED = AudioDeprecatedMagicNumber;
AudioVolume_DEPRECATED = AudioDeprecatedMagicNumber;
bLooping = true;
bSuppressSubtitles = false;
bOverrideAttenuation = false;
BlendType = EMovieSceneBlendType::Absolute;
EvalOptions.EnableAndSetCompletionMode
(GetLinkerCustomVersion(FSequencerObjectVersion::GUID) < FSequencerObjectVersion::WhenFinishedDefaultsToProjectDefault ?
EMovieSceneCompletionMode::RestoreState :
EMovieSceneCompletionMode::ProjectDefault);
SoundVolume.SetDefault(1.f);
PitchMultiplier.SetDefault(1.f);
}
namespace MovieSceneAudioSectionPrivate
{
template<typename ChannelType, typename ValueType>
void AddInputChannels(UMovieSceneAudioSection* InSection, FMovieSceneChannelProxyData& InChannelProxyData)
{
InSection->ForEachInput([&InChannelProxyData](FName InName, const ChannelType& InChannel)
{
#if WITH_EDITOR
FMovieSceneChannelMetaData Data;
FText TextName = FText::FromName(InName);
Data.SetIdentifiers(FName(InName.ToString() + GetGeneratedTypeName<ChannelType>()), TextName, TextName);
InChannelProxyData.Add(const_cast<ChannelType&>(InChannel), Data, TMovieSceneExternalValue<ValueType>::Make());
#else //WITH_EDITOR
InChannelProxyData.Add(const_cast<ChannelType&>(InChannel));
#endif //WITH_EDITOR
});
}
}
EMovieSceneChannelProxyType UMovieSceneAudioSection::CacheChannelProxy()
{
// Set up the channel proxy
FMovieSceneChannelProxyData Channels;
UMovieSceneAudioTrack* AudioTrack = Cast<UMovieSceneAudioTrack>(GetOuter());
UMovieScene* MovieScene = AudioTrack ? Cast<UMovieScene>(AudioTrack->GetOuter()) : nullptr;
const bool bHasAttachData = MovieScene && MovieScene->ContainsTrack(*AudioTrack);
#if WITH_EDITOR
FAudioChannelEditorData EditorData;
Channels.Add(SoundVolume, EditorData.Data[0], TMovieSceneExternalValue<float>());
Channels.Add(PitchMultiplier, EditorData.Data[1], TMovieSceneExternalValue<float>());
if (bHasAttachData)
{
Channels.Add(AttachActorData, EditorData.Data[2]);
}
#else
Channels.Add(SoundVolume);
Channels.Add(PitchMultiplier);
if (bHasAttachData)
{
Channels.Add(AttachActorData);
}
#endif
using namespace MovieSceneAudioSectionPrivate;
SetupSoundInputParameters(Sound);
AddInputChannels<FMovieSceneFloatChannel, float>(this, Channels);
AddInputChannels<FMovieSceneBoolChannel, bool>(this, Channels);
AddInputChannels<FMovieSceneIntegerChannel, int32>(this, Channels);
AddInputChannels<FMovieSceneStringChannel, FString>(this, Channels);
AddInputChannels<FMovieSceneAudioTriggerChannel, bool>(this, Channels);
ChannelProxy = MakeShared<FMovieSceneChannelProxy>(MoveTemp(Channels));
return EMovieSceneChannelProxyType::Dynamic;
}
USoundBase* UMovieSceneAudioSection::GetPlaybackSound() const
{
return UMovieSceneLanguagePreviewDecoration::FindLocalizedAsset(Sound, this);
}
void UMovieSceneAudioSection::PopulateInitialAnchors(TMap<FGuid, FMovieSceneScalingAnchor>& OutAnchors)
{
UMovieSceneSectionAnchorsDecoration* Decoration = FindDecoration<UMovieSceneSectionAnchorsDecoration>();
if (Decoration && HasStartFrame() && HasEndFrame())
{
FFrameNumber StartTime = GetInclusiveStartFrame();
OutAnchors.Add(Decoration->StartAnchor, FMovieSceneScalingAnchor{ StartTime, (GetExclusiveEndFrame() - StartTime).Value });
}
}
void UMovieSceneAudioSection::PopulateAnchors(TMap<FGuid, FMovieSceneScalingAnchor>& OutAnchors)
{
UMovieSceneSectionAnchorsDecoration* Decoration = FindDecoration<UMovieSceneSectionAnchorsDecoration>();
if (Decoration && HasStartFrame() && HasEndFrame())
{
USoundBase* ReferenceSound = GetSound();
USoundBase* PlaybackSound = GetPlaybackSound();
if (PlaybackSound && ReferenceSound && ReferenceSound != PlaybackSound)
{
const UMovieScene* MovieScene = GetTypedOuter<UMovieScene>();
const float ReferenceDuration = MovieSceneHelpers::GetSoundDuration(ReferenceSound);
const float PlaybackDuration = MovieSceneHelpers::GetSoundDuration(PlaybackSound);
FFrameNumber StartTime = GetInclusiveStartFrame();
FFrameNumber EndTime = GetExclusiveEndFrame();
int32 SectionLength = FMath::CeilToInt32((EndTime.Value - StartTime.Value) * double(PlaybackDuration / ReferenceDuration));
OutAnchors.Add(Decoration->StartAnchor, FMovieSceneScalingAnchor{ StartTime, SectionLength });
}
}
}
UObject* UMovieSceneAudioSection::GetSourceObject() const
{
return GetSound();
}
void UMovieSceneAudioSection::SetupSoundInputParameters(USoundBase* InSoundBase)
{
// Populate with defaults.
// Don't init resources when running cook, as this can trigger
// registration of a MetaSound and its dependent graphs.
// Those will instead be registered when the MetaSound itself is cooked (FMetasoundAssetBase::CookMetaSound)
// in a way that does not deal with runtime data like this function does
// Getting the default parameters and the rest of the function are
// dependent on that runtime data and don't need to be cooked
if (InSoundBase && !IsRunningCookCommandlet())
{
InSoundBase->InitResources();
TArray<FAudioParameter> DefaultParams;
InSoundBase->GetAllDefaultParameters(DefaultParams);
TSet<FName> OrphanedFloatInputs;
Inputs_Float.GetKeys(OrphanedFloatInputs);
TSet<FName> OrphanedTriggerInputs;
Inputs_Trigger.GetKeys(OrphanedTriggerInputs);
TSet<FName> OrphanedBoolInputs;
Inputs_Bool.GetKeys(OrphanedBoolInputs);
TSet<FName> OrphanedIntInputs;
Inputs_Int.GetKeys(OrphanedIntInputs);
TSet<FName> OrphanedStringInputs;
Inputs_String.GetKeys(OrphanedStringInputs);
for (const FAudioParameter& Param : DefaultParams)
{
switch (Param.ParamType)
{
case EAudioParameterType::Float:
{
Inputs_Float.FindOrAdd(Param.ParamName, FMovieSceneFloatChannel{}).SetDefault(Param.FloatParam);
OrphanedFloatInputs.Remove(Param.ParamName);
break;
}
case EAudioParameterType::Trigger:
{
Inputs_Trigger.FindOrAdd(Param.ParamName, FMovieSceneAudioTriggerChannel{});
OrphanedTriggerInputs.Remove(Param.ParamName);
break;
}
case EAudioParameterType::Boolean:
{
Inputs_Bool.FindOrAdd(Param.ParamName, FMovieSceneBoolChannel{}).SetDefault(Param.BoolParam);
OrphanedBoolInputs.Remove(Param.ParamName);
break;
}
case EAudioParameterType::Integer:
{
Inputs_Int.FindOrAdd(Param.ParamName, FMovieSceneIntegerChannel{}).SetDefault(Param.IntParam);
OrphanedIntInputs.Remove(Param.ParamName);
break;
}
case EAudioParameterType::String:
{
Inputs_String.FindOrAdd(Param.ParamName, FMovieSceneStringChannel{}).SetDefault(Param.StringParam);
OrphanedStringInputs.Remove(Param.ParamName);
break;
}
default:
// Not supported yet.
break;
}
}
for (const FName& Name : OrphanedFloatInputs)
{
Inputs_Float.Remove(Name);
}
for (const FName& Name : OrphanedTriggerInputs)
{
Inputs_Trigger.Remove(Name);
}
for (const FName& Name : OrphanedBoolInputs)
{
Inputs_Bool.Remove(Name);
}
for (const FName& Name : OrphanedIntInputs)
{
Inputs_Int.Remove(Name);
}
for (const FName& Name : OrphanedStringInputs)
{
Inputs_String.Remove(Name);
}
}
}
TOptional<FFrameTime> UMovieSceneAudioSection::GetOffsetTime() const
{
return TOptional<FFrameTime>(StartFrameOffset);
}
void UMovieSceneAudioSection::MigrateFrameTimes(FFrameRate SourceRate, FFrameRate DestinationRate)
{
if (StartFrameOffset.Value > 0)
{
FFrameNumber NewStartFrameOffset = ConvertFrameTime(FFrameTime(StartFrameOffset), SourceRate, DestinationRate).FloorToFrame();
StartFrameOffset = NewStartFrameOffset;
}
}
void UMovieSceneAudioSection::PostLoad()
{
Super::PostLoad();
if (AudioDilationFactor_DEPRECATED != AudioDeprecatedMagicNumber)
{
PitchMultiplier.SetDefault(AudioDilationFactor_DEPRECATED);
AudioDilationFactor_DEPRECATED = AudioDeprecatedMagicNumber;
}
if (AudioVolume_DEPRECATED != AudioDeprecatedMagicNumber)
{
SoundVolume.SetDefault(AudioVolume_DEPRECATED);
AudioVolume_DEPRECATED = AudioDeprecatedMagicNumber;
}
TOptional<double> StartOffsetToUpgrade;
if (AudioStartTime_DEPRECATED != AudioDeprecatedMagicNumber)
{
// Previously, start time in relation to the sequence. Start time was used to calculate the offset into the
// clip at the start of the section evaluation as such: Section Start Time - Start Time.
if (AudioStartTime_DEPRECATED != 0.f && HasStartFrame())
{
StartOffsetToUpgrade = GetInclusiveStartFrame() / GetTypedOuter<UMovieScene>()->GetTickResolution() - AudioStartTime_DEPRECATED;
}
AudioStartTime_DEPRECATED = AudioDeprecatedMagicNumber;
}
if (StartOffset_DEPRECATED != AudioDeprecatedMagicNumber)
{
StartOffsetToUpgrade = StartOffset_DEPRECATED;
StartOffset_DEPRECATED = AudioDeprecatedMagicNumber;
}
FFrameRate LegacyFrameRate = GetLegacyConversionFrameRate();
if (StartOffsetToUpgrade.IsSet())
{
FFrameRate DisplayRate = GetTypedOuter<UMovieScene>()->GetDisplayRate();
FFrameRate TickResolution = GetTypedOuter<UMovieScene>()->GetTickResolution();
StartFrameOffset = ConvertFrameTime(FFrameTime::FromDecimal(DisplayRate.AsDecimal() * StartOffsetToUpgrade.GetValue()), DisplayRate, TickResolution).FrameNumber;
}
}
TOptional<TRange<FFrameNumber> > UMovieSceneAudioSection::GetAutoSizeRange() const
{
if (!Sound)
{
return TRange<FFrameNumber>();
}
const FFrameRate FrameRate = GetTypedOuter<UMovieScene>()->GetTickResolution();
// determine initial duration
// @todo Once we have infinite sections, we can remove this
// @todo ^^ Why? Infinte sections would mean there's no starting time?
FFrameTime DurationToUse = 1.f * FrameRate; // if all else fails, use 1 second duration
// Only one-shot non-procedural sounds have a known duration
if (Sound->IsOneShot() && !Sound->IsProcedurallyGenerated())
{
const float SoundDuration = MovieSceneHelpers::GetSoundDuration(Sound);
// This should not hit if the sound has returned true for IsOneShot()
check(SoundDuration != INDEFINITELY_LOOPING_DURATION);
if (SoundDuration > 0)
{
DurationToUse = FMath::Max(SoundDuration * FrameRate - StartFrameOffset, FFrameTime(1));
}
}
const int32 IFrameNumber = DurationToUse.FrameNumber.Value + static_cast<int32>(DurationToUse.GetSubFrame() + 0.5f);
return TRange<FFrameNumber>(GetInclusiveStartFrame(), GetInclusiveStartFrame() + IFrameNumber);
}
void UMovieSceneAudioSection::TrimSection(FQualifiedFrameTime TrimTime, bool bTrimLeft, bool bDeleteKeys)
{
SetFlags(RF_Transactional);
if (TryModify())
{
if (bTrimLeft)
{
StartFrameOffset = HasStartFrame() ? GetStartOffsetAtTrimTime(TrimTime, StartFrameOffset, GetInclusiveStartFrame()) : 0;
}
Super::TrimSection(TrimTime, bTrimLeft, bDeleteKeys);
}
}
UMovieSceneSection* UMovieSceneAudioSection::SplitSection(FQualifiedFrameTime SplitTime, bool bDeleteKeys)
{
const FFrameNumber InitialStartFrameOffset = StartFrameOffset;
const FFrameNumber NewOffset = HasStartFrame() ? GetStartOffsetAtTrimTime(SplitTime, StartFrameOffset, GetInclusiveStartFrame()) : 0;
UMovieSceneSection* NewSection = Super::SplitSection(SplitTime, bDeleteKeys);
if (NewSection != nullptr)
{
UMovieSceneAudioSection* NewAudioSection = Cast<UMovieSceneAudioSection>(NewSection);
NewAudioSection->StartFrameOffset = NewOffset;
}
// Restore original offset modified by splitting
StartFrameOffset = InitialStartFrameOffset;
return NewSection;
}
void UMovieSceneAudioSection::SetSound(USoundBase* InSound)
{
Sound = InSound;
// For a procedurally generated sound, only have the section loop if it's specifically a looping sound
// this is because there's no way to determine a duration for the sound so the user has no way of
// now when to end the region to avoid retriggering the one-shot procedural asset.
if (Sound->IsProcedurallyGenerated())
{
bLooping = !Sound->IsOneShot();
}
InvalidateChannelProxy();
}
USceneComponent* UMovieSceneAudioSection::GetAttachComponent(const AActor* InParentActor, const FMovieSceneActorReferenceKey& Key) const
{
FName AttachComponentName = Key.ComponentName;
FName AttachSocketName = Key.SocketName;
if (AttachSocketName != NAME_None)
{
if (AttachComponentName != NAME_None)
{
TInlineComponentArray<USceneComponent*> PotentialAttachComponents(InParentActor);
for (USceneComponent* PotentialAttachComponent : PotentialAttachComponents)
{
if (PotentialAttachComponent->GetFName() == AttachComponentName && PotentialAttachComponent->DoesSocketExist(AttachSocketName))
{
return PotentialAttachComponent;
}
}
}
else if (InParentActor->GetRootComponent()->DoesSocketExist(AttachSocketName))
{
return InParentActor->GetRootComponent();
}
}
else if (AttachComponentName != NAME_None)
{
TInlineComponentArray<USceneComponent*> PotentialAttachComponents(InParentActor);
for (USceneComponent* PotentialAttachComponent : PotentialAttachComponents)
{
if (PotentialAttachComponent->GetFName() == AttachComponentName)
{
return PotentialAttachComponent;
}
}
}
if (InParentActor->GetDefaultAttachComponent())
{
return InParentActor->GetDefaultAttachComponent();
}
else
{
return InParentActor->GetRootComponent();
}
}
bool UMovieSceneAudioSection::PopulateEvaluationFieldImpl(const TRange<FFrameNumber>& EffectiveRange, const FMovieSceneEvaluationFieldEntityMetaData& InMetaData, FMovieSceneEntityComponentFieldBuilder* OutFieldBuilder)
{
using namespace UE::MovieScene;
int32 MetaDataIndex = OutFieldBuilder->AddMetaData(InMetaData);
// Add the default entity first.
int32 MainEntityIndex = OutFieldBuilder->FindOrAddEntity(this, EncodeEntityID(0, EAudioSectionEntityType::MainEntity));
OutFieldBuilder->AddPersistentEntity(EffectiveRange, MainEntityIndex, MetaDataIndex);
// See how many additional entities we need to store the audio input data.
// We can pack 9 float channels per entity, but we can only have one string/bool/int/audio-trigger
// channel per entity.
int32 NumInputDataEntities = Inputs_Float.Num() % 9;
NumInputDataEntities = FMath::Max(NumInputDataEntities, Inputs_String.Num());
NumInputDataEntities = FMath::Max(NumInputDataEntities, Inputs_Bool.Num());
NumInputDataEntities = FMath::Max(NumInputDataEntities, Inputs_Int.Num());
// Add these extra entities to the evaluation field.
for (int32 InputDataEntity = 0; InputDataEntity < NumInputDataEntities; ++InputDataEntity)
{
int32 EntityIndex = OutFieldBuilder->FindOrAddEntity(this, EncodeEntityID(InputDataEntity, EAudioSectionEntityType::InputsEntity));
OutFieldBuilder->AddPersistentEntity(EffectiveRange, EntityIndex, MetaDataIndex);
}
// Audio triggers are added differently, as one-shot entities.
TArray<FName> AudioTriggerNames;
Inputs_Trigger.GetKeys(AudioTriggerNames);
AudioTriggerNames.Sort(FNameLexicalLess());
for (int32 TriggerIndex = 0; TriggerIndex < AudioTriggerNames.Num(); ++TriggerIndex)
{
FMovieSceneAudioTriggerChannel& TriggerChannel = Inputs_Trigger[AudioTriggerNames[TriggerIndex]];
TArrayView<const FFrameNumber> Times = TriggerChannel.GetTimes();
for (int32 Index = 0; Index < Times.Num(); ++Index)
{
if (EffectiveRange.Contains(Times[Index]))
{
TRange<FFrameNumber> TriggerRange(Times[Index]);
int32 EntityIndex = OutFieldBuilder->FindOrAddEntity(this, EncodeEntityID(TriggerIndex, EAudioSectionEntityType::TriggerEntity));
OutFieldBuilder->AddOneShotEntity(TriggerRange, EntityIndex, MetaDataIndex);
}
}
}
// Return true to indicate we've done everything ourselves.
return true;
}
void UMovieSceneAudioSection::ImportEntityImpl(UMovieSceneEntitySystemLinker* EntityLinker, const FEntityImportParams& Params, FImportedEntity* OutImportedEntity)
{
using namespace UE::MovieScene;
if (!Sound)
{
return;
}
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
const FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get();
const FGuid ObjectBindingID = Params.GetObjectBindingID();
int32 EntityIndex;
EAudioSectionEntityType EntityType;
DecodeEntityID(Params.EntityID, EntityIndex, EntityType);
if (EntityType == EAudioSectionEntityType::MainEntity)
{
// Default entity... we add the main audio component data, plus the volume and pitch channels.
OutImportedEntity->AddBuilder(
FEntityBuilder()
.AddConditional(BuiltInComponents->GenericObjectBinding, ObjectBindingID, ObjectBindingID.IsValid())
.AddTagConditional(BuiltInComponents->Tags.Root, !ObjectBindingID.IsValid())
.Add(TrackComponents->Audio, FMovieSceneAudioComponentData{ this })
.Add(BuiltInComponents->FloatChannel[0], &SoundVolume)
.Add(BuiltInComponents->FloatChannel[1], &PitchMultiplier)
);
}
else if (EntityType == EAudioSectionEntityType::InputsEntity)
{
// Additional entities for custom audio input values.
TArray<FName> InputNames;
FMovieSceneAudioInputData InputData;
// There are up to 9 float channels per entity, and we know that all entities are fully packed
// until the last one. So add as many as we can for the given entity we're building.
int32 FloatInputStartIndex = EntityIndex * 9;
if (FloatInputStartIndex < Inputs_Float.Num())
{
int32 FloatInputNum = FMath::Min(9, Inputs_Float.Num() - FloatInputStartIndex);
Inputs_Float.GetKeys(InputNames);
for (int32 Offset = 0; Offset < FloatInputNum; ++Offset)
{
InputData.FloatInputs[Offset] = InputNames[FloatInputStartIndex + Offset];
}
}
// Other inputs can only be added once per entity, so add one of each type that exists.
int32 OtherInputStartIndex = EntityIndex;
if (OtherInputStartIndex < Inputs_String.Num())
{
InputNames.Reset();
Inputs_String.GetKeys(InputNames);
InputData.StringInput = InputNames[OtherInputStartIndex];
}
if (OtherInputStartIndex < Inputs_Bool.Num())
{
InputNames.Reset();
Inputs_Bool.GetKeys(InputNames);
InputData.BoolInput = InputNames[OtherInputStartIndex];
}
if (OtherInputStartIndex < Inputs_Int.Num())
{
InputNames.Reset();
Inputs_Int.GetKeys(InputNames);
InputData.IntInput = InputNames[OtherInputStartIndex];
}
// Make this additional entity by adding the component that specifies what audio input channels
// are present, plus all of these channels.
OutImportedEntity->AddBuilder(
FEntityBuilder()
.AddConditional(BuiltInComponents->GenericObjectBinding, ObjectBindingID, ObjectBindingID.IsValid())
.AddTagConditional(BuiltInComponents->Tags.Root, !ObjectBindingID.IsValid())
.Add(TrackComponents->Audio, FMovieSceneAudioComponentData{ this })
.Add(TrackComponents->AudioInputs, InputData)
);
for (int32 Index = 0; Index < 9; ++Index)
{
FName InputName = InputData.FloatInputs[Index];
if (!InputName.IsNone())
{
OutImportedEntity->AddBuilder(
FEntityBuilder()
.Add(BuiltInComponents->FloatChannel[Index], &Inputs_Float[InputName])
);
}
}
if (!InputData.StringInput.IsNone())
{
OutImportedEntity->AddBuilder(
FEntityBuilder()
.Add(BuiltInComponents->StringChannel, &Inputs_String[InputData.StringInput])
);
}
if (!InputData.BoolInput.IsNone())
{
OutImportedEntity->AddBuilder(
FEntityBuilder()
.Add(BuiltInComponents->BoolChannel, &Inputs_Bool[InputData.BoolInput])
);
}
if (!InputData.IntInput.IsNone())
{
OutImportedEntity->AddBuilder(
FEntityBuilder()
.Add(BuiltInComponents->IntegerChannel, &Inputs_Int[InputData.IntInput])
);
}
}
else if (EntityType == EAudioSectionEntityType::TriggerEntity)
{
// Additional one-shot entities for audio triggers.
// The decoded index is the index of the name in the triggers map.
TArray<FName> AudioTriggerNames;
Inputs_Trigger.GetKeys(AudioTriggerNames);
AudioTriggerNames.Sort(FNameLexicalLess());
OutImportedEntity->AddBuilder(
FEntityBuilder()
.AddConditional(BuiltInComponents->GenericObjectBinding, ObjectBindingID, ObjectBindingID.IsValid())
.AddTagConditional(BuiltInComponents->Tags.Root, !ObjectBindingID.IsValid())
.Add(TrackComponents->Audio, FMovieSceneAudioComponentData{ this })
.Add(TrackComponents->AudioTriggerName, AudioTriggerNames[EntityIndex])
);
}
}