Files
UnrealEngineUWP/Engine/Source/Runtime/AudioMixer/Private/Components/SynthComponent.cpp
aaron mcleran 6e374e0884 Refactor of media sound generator to use the new ISoundGenerator interface.
Cleans up the ISoundGenerator in SynthComponent in OnUnregister to make sure it releases the generator data before GC.

#rb Jimmy.Smith, Ryan.Mangin, Thomas.Engel
#preflight 634a53aace524ed3562c7eac

[CL 22549071 by aaron mcleran in ue5-main branch]
2022-10-15 13:04:15 -04:00

637 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Components/SynthComponent.h"
#include "AudioDevice.h"
#include "AudioMixerLog.h"
#include "Sound/AudioSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(SynthComponent)
USynthSound::USynthSound(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void USynthSound::Init(USynthComponent* InSynthComponent, const int32 InNumChannels, const int32 InSampleRate, const int32 InCallbackSize)
{
check(InSynthComponent);
OwningSynthComponent = InSynthComponent;
VirtualizationMode = EVirtualizationMode::PlayWhenSilent;
NumChannels = InNumChannels;
NumSamplesToGeneratePerCallback = InCallbackSize;
// Turn off async generation in old audio engine on mac.
#if PLATFORM_MAC
const FAudioDevice* AudioDevice = InSynthComponent->GetAudioDevice();
if (AudioDevice && !AudioDevice->IsAudioMixerEnabled())
{
bCanProcessAsync = false;
}
else
#endif // #if PLATFORM_MAC
{
bCanProcessAsync = true;
}
Duration = INDEFINITELY_LOOPING_DURATION;
bLooping = true;
SampleRate = InSampleRate;
}
void USynthSound::StartOnAudioDevice(FAudioDevice* InAudioDevice)
{
check(InAudioDevice != nullptr);
bAudioMixer = InAudioDevice->IsAudioMixerEnabled();
}
void USynthSound::OnBeginGenerate()
{
if (ensure(OwningSynthComponent.IsValid()))
{
OwningSynthComponent->OnBeginGenerate();
}
}
int32 USynthSound::OnGeneratePCMAudio(TArray<uint8>& OutAudio, int32 NumSamples)
{
LLM_SCOPE(ELLMTag::AudioSynthesis);
OutAudio.Reset();
if (bAudioMixer)
{
// If running with audio mixer, the output audio buffer will be in floats already
OutAudio.AddZeroed(NumSamples * sizeof(float));
// Mark pending kill can null this out on the game thread in rare cases.
if (!OwningSynthComponent.IsValid())
{
return 0;
}
return OwningSynthComponent->OnGeneratePCMAudio((float*)OutAudio.GetData(), NumSamples);
}
else
{
// Use the float scratch buffer instead of the out buffer directly
FloatBuffer.Reset();
FloatBuffer.AddZeroed(NumSamples * sizeof(float));
// Mark pending kill can null this out on the game thread in rare cases.
if (!OwningSynthComponent.IsValid())
{
return 0;
}
float* FloatBufferDataPtr = FloatBuffer.GetData();
int32 NumSamplesGenerated = OwningSynthComponent->OnGeneratePCMAudio(FloatBufferDataPtr, NumSamples);
// Convert the float buffer to int16 data
OutAudio.AddZeroed(NumSamples * sizeof(int16));
int16* OutAudioBuffer = (int16*)OutAudio.GetData();
for (int32 i = 0; i < NumSamples; ++i)
{
OutAudioBuffer[i] = (int16)(32767.0f * FMath::Clamp(FloatBufferDataPtr[i], -1.0f, 1.0f));
}
return NumSamplesGenerated;
}
return NumSamples;
}
void USynthSound::OnEndGenerate()
{
// Mark pending kill can null this out on the game thread in rare cases.
if (OwningSynthComponent.IsValid())
{
OwningSynthComponent->OnEndGenerate();
}
}
ISoundGeneratorPtr USynthSound::CreateSoundGenerator(const FSoundGeneratorInitParams& InParams)
{
if (OwningSynthComponent.IsValid())
{
return OwningSynthComponent->CreateSoundGeneratorInternal(InParams);
}
return nullptr;
}
Audio::EAudioMixerStreamDataFormat::Type USynthSound::GetGeneratedPCMDataFormat() const
{
// Only audio mixer supports return float buffers
return bAudioMixer ? Audio::EAudioMixerStreamDataFormat::Float : Audio::EAudioMixerStreamDataFormat::Int16;
}
USynthComponent::USynthComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bAutoActivate = false;
bStopWhenOwnerDestroyed = true;
bEnableBusSends = true;
bEnableBaseSubmix = true;
bEnableSubmixSends = true;
bNeverNeedsRenderUpdate = true;
bUseAttachParentBound = true; // Avoid CalcBounds() when transform changes.
bIsSynthPlaying = false;
bIsInitialized = false;
bIsUISound = false;
bAlwaysPlay = true;
Synth = nullptr;
PreferredBufferLength = DEFAULT_PROCEDURAL_SOUNDWAVE_BUFFER_SIZE;
#if WITH_EDITORONLY_DATA
bVisualizeComponent = false;
#endif
}
void USynthComponent::OnAudioComponentEnvelopeValue(const UAudioComponent* InAudioComponent, const USoundWave* SoundWave, const float EnvelopeValue)
{
if (OnAudioEnvelopeValue.IsBound())
{
OnAudioEnvelopeValue.Broadcast(EnvelopeValue);
}
if (OnAudioEnvelopeValueNative.IsBound())
{
OnAudioEnvelopeValueNative.Broadcast(InAudioComponent, EnvelopeValue);
}
}
void USynthComponent::BeginDestroy()
{
Super::BeginDestroy();
Stop();
}
void USynthComponent::Activate(bool bReset)
{
if (bReset || ShouldActivate())
{
Start();
if (IsActive())
{
OnComponentActivated.Broadcast(this, bReset);
}
}
}
void USynthComponent::Deactivate()
{
if (ShouldActivate() == false)
{
Stop();
if (!IsActive())
{
OnComponentDeactivated.Broadcast(this);
}
}
}
void USynthComponent::Initialize(int32 SampleRateOverride)
{
// This will try to create the audio component if it hasn't yet been created
CreateAudioComponent();
// Try to get a proper sample rate
int32 SampleRate = SampleRateOverride;
if (SampleRate == INDEX_NONE)
{
// Check audio device if we've not explicitly been told what sample rate to use
FAudioDevice* AudioDevice = GetAudioDevice();
if (AudioDevice)
{
SampleRate = AudioDevice->SampleRate;
}
}
// Only allow initialization if we have a proper sample rate
if (SampleRate != INDEX_NONE)
{
#if SYNTH_GENERATOR_TEST_TONE
NumChannels = 2;
TestSineLeft.Init(SampleRate, 440.0f, 0.5f);
TestSineRight.Init(SampleRate, 220.0f, 0.5f);
#else
// Initialize the synth component
Init(SampleRate);
if (NumChannels < 0 || NumChannels > 8)
{
UE_LOG(LogAudioMixer, Error, TEXT("Synthesis component '%s' has set an invalid channel count '%d'."), *GetName(), NumChannels);
}
NumChannels = FMath::Clamp(NumChannels, 1, 8);
#endif
if (!Synth)
{
Synth = NewObject<USynthSound>();
}
// Copy sound base data to the sound
Synth->SourceEffectChain = SourceEffectChain;
Synth->SoundSubmixObject = SoundSubmix;
Synth->SoundSubmixSends = SoundSubmixSends;
Synth->BusSends = BusSends;
Synth->PreEffectBusSends = PreEffectBusSends;
Synth->bEnableBusSends = bEnableBusSends;
Synth->bEnableBaseSubmix = bEnableBaseSubmix;
Synth->bEnableSubmixSends = bEnableSubmixSends;
Synth->Init(this, NumChannels, SampleRate, PreferredBufferLength);
// Retrieve the synth component's audio device vs the audio component's
if (FAudioDevice* AudioDevice = GetAudioDevice())
{
Synth->StartOnAudioDevice(AudioDevice);
}
}
}
UAudioComponent* USynthComponent::GetAudioComponent()
{
return AudioComponent;
}
void USynthComponent::CreateAudioComponent()
{
if (!AudioComponent)
{
// Create the audio component which will be used to play the procedural sound wave
AudioComponent = NewObject<UAudioComponent>(this, NAME_None, RF_Transactional | RF_Transient | RF_TextExportTransient);
AudioComponent->CreationMethod = CreationMethod;
AudioComponent->OnAudioSingleEnvelopeValueNative.AddUObject(this, &USynthComponent::OnAudioComponentEnvelopeValue);
if (!AudioComponent->GetAttachParent() && !AudioComponent->IsAttachedTo(this))
{
AActor* Owner = GetOwner();
// If the media component has no owner or the owner doesn't have a world
if (!Owner || !Owner->GetWorld())
{
// Attempt to retrieve the synth component's world and register the audio component with it
// This ensures that the synth component plays on the correct world in cases where there isn't an owner
if (UWorld* World = GetWorld())
{
AudioComponent->RegisterComponentWithWorld(World);
AudioComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
}
else
{
AudioComponent->SetupAttachment(this);
}
}
else
{
AudioComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
AudioComponent->RegisterComponent();
}
}
}
if (AudioComponent)
{
AudioComponent->bAutoActivate = false;
AudioComponent->bStopWhenOwnerDestroyed = true;
AudioComponent->bShouldRemainActiveIfDropped = true;
AudioComponent->Mobility = EComponentMobility::Movable;
#if WITH_EDITORONLY_DATA
AudioComponent->bVisualizeComponent = false;
#endif
// Set defaults to be the same as audio component defaults
AudioComponent->EnvelopeFollowerAttackTime = EnvelopeFollowerAttackTime;
AudioComponent->EnvelopeFollowerReleaseTime = EnvelopeFollowerReleaseTime;
AudioComponent->bAlwaysPlay = bAlwaysPlay;
}
}
void USynthComponent::OnRegister()
{
CreateAudioComponent();
Super::OnRegister();
}
void USynthComponent::OnUnregister()
{
// Route OnUnregister event.
Super::OnUnregister();
// Don't stop audio and clean up component if owner has been destroyed (default behaviour). This function gets
// called from AActor::ClearComponents when an actor gets destroyed which is not usually what we want for one-
// shot sounds.
AActor* Owner = GetOwner();
if (!Owner || bStopWhenOwnerDestroyed)
{
Stop();
}
// Make sure the audio component is destroyed during unregister
if (AudioComponent && !AudioComponent->IsBeingDestroyed())
{
if (Owner && Owner->GetWorld())
{
AudioComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
AudioComponent->UnregisterComponent();
}
AudioComponent->DestroyComponent();
AudioComponent = nullptr;
}
// Clear out the synth component's reference to the sound generator or it will leak until it gets GC'd
// Normally this is ok to wait till GC but some derived synths might need for the handle to be released
SoundGenerator.Reset();
}
void USynthComponent::EndPlay(const EEndPlayReason::Type Reason)
{
Super::EndPlay(Reason);
if (GetOwner() && (Reason == EEndPlayReason::LevelTransition || Reason == EEndPlayReason::RemovedFromWorld || Reason == EEndPlayReason::Destroyed))
{
// If our world or sublevel is going away, stop immediately to prevent the containing world/level from being leaked via hard references from the audio device.
Stop();
}
}
USoundClass* USynthComponent::GetSoundClass()
{
if (SoundClass)
{
return SoundClass;
}
const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>();
if (ensure(AudioSettings))
{
return AudioSettings->GetDefaultSoundClass();
}
return nullptr;
}
bool USynthComponent::IsReadyForOwnerToAutoDestroy() const
{
const bool bIsAudioComponentReadyForDestroy = !AudioComponent || (AudioComponent && !AudioComponent->IsPlaying());
const bool bIsSynthSoundReadyForDestroy = !Synth || !Synth->IsGeneratingAudio();
return bIsAudioComponentReadyForDestroy && bIsSynthSoundReadyForDestroy;
}
#if WITH_EDITOR
void USynthComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
if (IsActive())
{
// If this is an auto destroy component we need to prevent it from being auto-destroyed since we're really just restarting it
const bool bWasAutoDestroy = bAutoDestroy;
bAutoDestroy = false;
Stop();
bAutoDestroy = bWasAutoDestroy;
Start();
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif //WITH_EDITOR
#if WITH_EDITORONLY_DATA
void USynthComponent::PostLoad()
{
Super::PostLoad();
if (bOutputToBusOnly_DEPRECATED)
{
bEnableBusSends = true;
bEnableBaseSubmix = !bOutputToBusOnly_DEPRECATED;
bEnableSubmixSends = !bOutputToBusOnly_DEPRECATED;
bOutputToBusOnly_DEPRECATED = false;
}
}
#endif //WITH_EDITORONLY_DATA
void USynthComponent::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
#if WITH_EDITORONLY_DATA
if (Ar.IsLoading())
{
if (ConcurrencySettings_DEPRECATED != nullptr)
{
ConcurrencySet.Add(ConcurrencySettings_DEPRECATED);
ConcurrencySettings_DEPRECATED = nullptr;
}
}
#endif // WITH_EDITORONLY_DATA
}
void USynthComponent::PumpPendingMessages()
{
TFunction<void()> Command;
while (CommandQueue.Dequeue(Command))
{
Command();
}
}
FAudioDevice* USynthComponent::GetAudioDevice() const
{
// If the synth component has a world, that means it was already registed with that world
if (UWorld* World = GetWorld())
{
return World->AudioDeviceHandle.GetAudioDevice();
}
// Otherwise, retrieve the audio component's audio device (probably from it's owner)
if (AudioComponent)
{
return AudioComponent->GetAudioDevice();
}
// No audio device
return nullptr;
}
int32 USynthComponent::OnGeneratePCMAudio(float* GeneratedPCMData, int32 NumSamples)
{
LLM_SCOPE(ELLMTag::AudioSynthesis);
PumpPendingMessages();
check(NumSamples > 0);
// Only call into the synth if we're actually playing, otherwise, we'll write out zero's
if (bIsSynthPlaying)
{
return OnGenerateAudio(GeneratedPCMData, NumSamples);
}
return NumSamples;
}
void USynthComponent::Start()
{
// Only need to start if we're not already active
if (IsActive())
{
return;
}
// We will also ensure that this synth was initialized before attempting to play.
Initialize();
// If there is no Synth USoundBase, we can't start. This can happen if start is called in a cook, a server, or
// if the audio engine is set to "noaudio".
// TODO: investigate if this should be handled elsewhere before this point
if (!Synth)
{
return;
}
if (AudioComponent)
{
// Copy the attenuation and concurrency data from the synth component to the audio component
AudioComponent->AttenuationSettings = AttenuationSettings;
AudioComponent->bOverrideAttenuation = bOverrideAttenuation;
AudioComponent->bIsUISound = bIsUISound;
AudioComponent->bIsPreviewSound = bIsPreviewSound;
AudioComponent->bAllowSpatialization = bAllowSpatialization;
AudioComponent->ConcurrencySet = ConcurrencySet;
AudioComponent->AttenuationOverrides = AttenuationOverrides;
AudioComponent->SoundClassOverride = SoundClass;
AudioComponent->EnvelopeFollowerAttackTime = EnvelopeFollowerAttackTime;
AudioComponent->EnvelopeFollowerReleaseTime = EnvelopeFollowerReleaseTime;
// Copy sound base data to the sound
Synth->AttenuationSettings = AttenuationSettings;
Synth->SourceEffectChain = SourceEffectChain;
Synth->SoundSubmixObject = SoundSubmix;
Synth->SoundSubmixSends = SoundSubmixSends;
// Set the audio component's sound to be our procedural sound wave
AudioComponent->SetSound(Synth);
AudioComponent->Play(0);
SetActiveFlag(AudioComponent->IsActive());
bIsSynthPlaying = true;
}
}
void USynthComponent::Stop()
{
if (IsActive())
{
if (AudioComponent)
{
AudioComponent->Stop();
if (FAudioDevice* AudioDevice = AudioComponent->GetAudioDevice())
{
AudioDevice->StopSoundsUsingResource(Synth);
}
}
SetActiveFlag(false);
}
}
bool USynthComponent::IsPlaying() const
{
return AudioComponent && AudioComponent->IsPlaying();
}
void USynthComponent::SetVolumeMultiplier(float VolumeMultiplier)
{
if (AudioComponent)
{
AudioComponent->SetVolumeMultiplier(VolumeMultiplier);
}
}
void USynthComponent::SetSubmixSend(USoundSubmixBase* Submix, float SendLevel)
{
if (AudioComponent)
{
AudioComponent->SetSubmixSend(Submix, SendLevel);
}
}
void USynthComponent::SetLowPassFilterEnabled(bool InLowPassFilterEnabled)
{
if (AudioComponent)
{
AudioComponent->SetLowPassFilterEnabled(InLowPassFilterEnabled);
}
}
void USynthComponent::SetLowPassFilterFrequency(float InLowPassFilterFrequency)
{
if (AudioComponent)
{
AudioComponent->SetLowPassFilterFrequency(InLowPassFilterFrequency);
}
}
void USynthComponent::SetOutputToBusOnly(bool bInOutputToBusOnly)
{
if (AudioComponent)
{
AudioComponent->SetOutputToBusOnly(bInOutputToBusOnly);
}
}
void USynthComponent::FadeIn(float FadeInDuration, float FadeVolumeLevel/* = 1.0f*/, float StartTime/* = 0.0f*/, const EAudioFaderCurve FadeCurve/* = EAudioFaderCurve::Linear*/) const
{
if(AudioComponent)
{
AudioComponent->FadeIn(FadeInDuration, FadeVolumeLevel, StartTime, FadeCurve);
}
}
void USynthComponent::FadeOut(float FadeOutDuration, float FadeVolumeLevel, const EAudioFaderCurve FadeCurve/* = EAudioFaderCurve::Linear*/) const
{
if(AudioComponent)
{
AudioComponent->FadeOut(FadeOutDuration, FadeVolumeLevel, FadeCurve);
}
}
void USynthComponent::AdjustVolume(float AdjustVolumeDuration, float AdjustVolumeLevel, const EAudioFaderCurve FadeCurve/* = EAudioFaderCurve::Linear*/) const
{
if(AudioComponent)
{
AudioComponent->AdjustVolume(AdjustVolumeDuration, AdjustVolumeLevel, FadeCurve);
}
}
void USynthComponent::SynthCommand(TFunction<void()> Command)
{
if (SoundGenerator.IsValid())
{
UE_LOG(LogAudioMixer, Error, TEXT("Synthesis component '%s' has implemented a sound generator interface. Do not call SynthCommand on the USynthComponent)."), *GetName());
}
else
{
CommandQueue.Enqueue(MoveTemp(Command));
}
}
ISoundGeneratorPtr USynthComponent::CreateSoundGeneratorInternal(const FSoundGeneratorInitParams& InParams)
{
LLM_SCOPE(ELLMTag::AudioSynthesis);
return SoundGenerator = CreateSoundGenerator(InParams);
}