353 lines
13 KiB
C++
353 lines
13 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#pragma once
|
|
|
|
#include "AudioDevice.h"
|
|
#include "Delegates/IDelegateInstance.h"
|
|
#include "DSP/DynamicsProcessor.h"
|
|
#include "DSP/MultithreadedPatching.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Sound/SoundEffectSubmix.h"
|
|
#include "Sound/SoundSubmix.h"
|
|
#include "Sound/SoundSubmixSend.h"
|
|
#include "Stats/Stats.h"
|
|
|
|
#include "AudioMixerSubmixEffectDynamicsProcessor.generated.h"
|
|
|
|
// The time it takes to process the master dynamics.
|
|
DECLARE_CYCLE_STAT_EXTERN(TEXT("Submix Dynamics"), STAT_AudioMixerSubmixDynamics, STATGROUP_AudioMixer, AUDIOMIXER_API);
|
|
|
|
namespace Audio
|
|
{
|
|
// Forward Declarations
|
|
class FMixerDevice;
|
|
}
|
|
|
|
UENUM(BlueprintType)
|
|
enum class ESubmixEffectDynamicsProcessorType : uint8
|
|
{
|
|
Compressor = 0,
|
|
Limiter,
|
|
Expander,
|
|
Gate,
|
|
UpwardsCompressor,
|
|
Count UMETA(Hidden)
|
|
};
|
|
|
|
UENUM(BlueprintType)
|
|
enum class ESubmixEffectDynamicsPeakMode : uint8
|
|
{
|
|
MeanSquared = 0,
|
|
RootMeanSquared,
|
|
Peak,
|
|
Count UMETA(Hidden)
|
|
};
|
|
|
|
UENUM(BlueprintType)
|
|
enum class ESubmixEffectDynamicsChannelLinkMode : uint8
|
|
{
|
|
Disabled = 0,
|
|
Average,
|
|
Peak,
|
|
Count UMETA(Hidden)
|
|
};
|
|
|
|
UENUM(BlueprintType)
|
|
enum class ESubmixEffectDynamicsKeySource : uint8
|
|
{
|
|
// Defaults to use local submix (input) as key
|
|
Default = 0,
|
|
|
|
// Uses audio bus as key
|
|
AudioBus,
|
|
|
|
// Uses external submix as key
|
|
Submix,
|
|
|
|
Count UMETA(Hidden)
|
|
};
|
|
|
|
class FKeySource
|
|
{
|
|
ESubmixEffectDynamicsKeySource Type = ESubmixEffectDynamicsKeySource::Default;
|
|
int32 NumChannels = 0;
|
|
uint32 ObjectId = INDEX_NONE;
|
|
|
|
mutable FCriticalSection MutateSourceCritSection;
|
|
|
|
public:
|
|
Audio::FPatchOutputStrongPtr Patch;
|
|
|
|
void Reset()
|
|
{
|
|
Patch.Reset();
|
|
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
NumChannels = 0;
|
|
ObjectId = INDEX_NONE;
|
|
Type = ESubmixEffectDynamicsKeySource::Default;
|
|
}
|
|
}
|
|
|
|
uint32 GetObjectId() const
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
return ObjectId;
|
|
}
|
|
|
|
int32 GetNumChannels() const
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
return NumChannels;
|
|
}
|
|
|
|
ESubmixEffectDynamicsKeySource GetType() const
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
return Type;
|
|
}
|
|
|
|
void SetNumChannels(const int32 InNumChannels)
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
NumChannels = InNumChannels;
|
|
}
|
|
|
|
void Update(ESubmixEffectDynamicsKeySource InType, uint32 InObjectId, int32 InNumChannels = 0)
|
|
{
|
|
bool bResetPatch = false;
|
|
|
|
{
|
|
const FScopeLock ScopeLock(&MutateSourceCritSection);
|
|
if (Type != InType || ObjectId != InObjectId || NumChannels != InNumChannels)
|
|
{
|
|
Type = InType;
|
|
ObjectId = InObjectId;
|
|
NumChannels = InNumChannels;
|
|
|
|
bResetPatch = true;
|
|
}
|
|
}
|
|
|
|
if (bResetPatch)
|
|
{
|
|
Patch.Reset();
|
|
}
|
|
}
|
|
};
|
|
|
|
USTRUCT(BlueprintType)
|
|
struct FSubmixEffectDynamicProcessorFilterSettings
|
|
{
|
|
GENERATED_USTRUCT_BODY()
|
|
|
|
// Whether or not filter is enabled
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Filter, meta = (DisplayName = "Enabled"))
|
|
uint8 bEnabled : 1;
|
|
|
|
// The cutoff frequency of the HPF applied to key signal
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Filter, meta = (DisplayName = "Cutoff (Hz)", EditCondition = "bEnabled", ClampMin = "20.0", ClampMax = "20000.0", UIMin = "20.0", UIMax = "20000.0"))
|
|
float Cutoff;
|
|
|
|
// The gain of the filter shelf applied to the key signal
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Filter, meta = (DisplayName = "Gain (dB)", EditCondition = "bEnabled", ClampMin = "-60.0", ClampMax = "6.0", UIMin = "-60.0", UIMax = "6.0"))
|
|
float GainDb;
|
|
|
|
FSubmixEffectDynamicProcessorFilterSettings()
|
|
: bEnabled(false)
|
|
, Cutoff(20.0f)
|
|
, GainDb(0.0f)
|
|
{
|
|
}
|
|
};
|
|
|
|
// Submix dynamics processor settings
|
|
USTRUCT(BlueprintType)
|
|
struct FSubmixEffectDynamicsProcessorSettings
|
|
{
|
|
GENERATED_USTRUCT_BODY()
|
|
|
|
// Type of processor to apply
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = General, meta = (DisplayName = "Type"))
|
|
ESubmixEffectDynamicsProcessorType DynamicsProcessorType = ESubmixEffectDynamicsProcessorType::Compressor;
|
|
|
|
// Mode of peak detection used on input key signal
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Dynamics, meta = (EditCondition = "!bBypass"))
|
|
ESubmixEffectDynamicsPeakMode PeakMode = ESubmixEffectDynamicsPeakMode::Peak;
|
|
|
|
// Mode of peak detection if key signal is multi-channel
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Dynamics, meta = (EditCondition = "!bBypass"))
|
|
ESubmixEffectDynamicsChannelLinkMode LinkMode = ESubmixEffectDynamicsChannelLinkMode::Average;
|
|
|
|
// The input gain of the dynamics processor
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = General, meta = (DisplayName = "Input Gain (dB)", UIMin = "-12.0", UIMax = "20.0", EditCondition = "!bBypass"))
|
|
float InputGainDb = 0.0f;
|
|
|
|
// The threshold at which to perform a dynamics processing operation
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Dynamics, meta = (DisplayName = "Threshold (dB)", ClampMin = "-60.0", ClampMax = "0.0", UIMin = "-60.0", UIMax = "0.0", EditCondition = "!bBypass"))
|
|
float ThresholdDb = -6.0f;
|
|
|
|
// The dynamics processor ratio used for compression/expansion
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Dynamics, meta = (
|
|
EditCondition = "!bBypass && DynamicsProcessorType == ESubmixEffectDynamicsProcessorType::Compressor || DynamicsProcessorType == ESubmixEffectDynamicsProcessorType::Expander || DynamicsProcessorType == ESubmixEffectDynamicsProcessorType::UpwardsCompressor",
|
|
ClampMin = "1.0", ClampMax = "20.0", UIMin = "1.0", UIMax = "20.0"))
|
|
float Ratio = 1.5f;
|
|
|
|
// The knee bandwidth of the processor to use
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Dynamics, meta = (DisplayName = "Knee (dB)", ClampMin = "0.0", ClampMax = "20.0", UIMin = "0.0", UIMax = "20.0", EditCondition = "!bBypass"))
|
|
float KneeBandwidthDb = 10.0f;
|
|
|
|
// The amount of time to look ahead of the current audio (Allows for transients to be included in dynamics processing)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Response, meta = (DisplayName = "Look Ahead (ms)", ClampMin = "0.0", ClampMax = "50.0", UIMin = "0.0", UIMax = "50.0", EditCondition = "!bBypass"))
|
|
float LookAheadMsec = 3.0f;
|
|
|
|
// The amount of time to ramp into any dynamics processing effect
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Response, meta = (DisplayName = "AttackTime (ms)", ClampMin = "1.0", ClampMax = "300.0", UIMin = "1.0", UIMax = "200.0", EditCondition = "!bBypass"))
|
|
float AttackTimeMsec = 10.0f;
|
|
|
|
// The amount of time to release the dynamics processing effect
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Response, meta = (DisplayName = "Release Time (ms)", ClampMin = "20.0", ClampMax = "5000.0", UIMin = "20.0", UIMax = "5000.0", EditCondition = "!bBypass"))
|
|
float ReleaseTimeMsec = 100.0f;
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (EditCondition = "!bBypass"))
|
|
ESubmixEffectDynamicsKeySource KeySource = ESubmixEffectDynamicsKeySource::Default;
|
|
|
|
// If set, uses output of provided audio bus as modulator of input signal for dynamics processor (Uses input signal as default modulator)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (EditCondition = "!bBypass && KeySource == ESubmixEffectDynamicsKeySource::AudioBus", EditConditionHides))
|
|
TObjectPtr<UAudioBus> ExternalAudioBus = nullptr;
|
|
|
|
// If set, uses output of provided submix as modulator of input signal for dynamics processor (Uses input signal as default modulator)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (EditCondition = "!bBypass && KeySource == ESubmixEffectDynamicsKeySource::Submix", EditConditionHides))
|
|
TObjectPtr<USoundSubmix> ExternalSubmix = nullptr;
|
|
|
|
UPROPERTY()
|
|
uint8 bChannelLinked_DEPRECATED : 1;
|
|
|
|
// Toggles treating the attack and release envelopes as analog-style vs digital-style (Analog will respond a bit more naturally/slower)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Response, meta = (EditCondition = "!bBypass"))
|
|
uint8 bAnalogMode : 1;
|
|
|
|
// Whether or not to bypass effect
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = General, meta = (DisplayName = "Bypass", DisplayAfter = "DynamicsProcessorType"))
|
|
uint8 bBypass : 1;
|
|
|
|
// Audition the key modulation signal, bypassing enveloping and processing the input signal.
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (DisplayName = "Key Audition", EditCondition = "!bBypass"))
|
|
uint8 bKeyAudition : 1;
|
|
|
|
// Gain to apply to key signal if key source not set to default (input).
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (
|
|
DisplayName = "External Input Gain (dB)",
|
|
EditCondition = "!bBypass && KeySource != ESubmixEffectDynamicsKeySource::Default",
|
|
UIMin = "-60.0", UIMax = "30.0")
|
|
)
|
|
float KeyGainDb = 0.0f;
|
|
|
|
// The output gain of the dynamics processor
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Output, meta = (DisplayName = "Output Gain (dB)", UIMin = "-60.0", UIMax = "30.0", EditCondition = "!bBypass"))
|
|
float OutputGainDb = 0.0f;
|
|
|
|
// High Shelf filter settings for key signal (external signal if supplied or input signal if not)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (DisplayName = "Key Highshelf", EditCondition = "!bBypass"))
|
|
FSubmixEffectDynamicProcessorFilterSettings KeyHighshelf;
|
|
|
|
// Low Shelf filter settings for key signal (external signal if supplied or input signal if not)
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Sidechain, meta = (DisplayName = "Key Lowshelf", EditCondition = "!bBypass"))
|
|
FSubmixEffectDynamicProcessorFilterSettings KeyLowshelf;
|
|
|
|
FSubmixEffectDynamicsProcessorSettings()
|
|
: bChannelLinked_DEPRECATED(true)
|
|
, bAnalogMode(true)
|
|
, bBypass(false)
|
|
, bKeyAudition(false)
|
|
{
|
|
KeyLowshelf.Cutoff = 20000.0f;
|
|
}
|
|
};
|
|
|
|
|
|
class FSubmixEffectDynamicsProcessor : public FSoundEffectSubmix
|
|
{
|
|
public:
|
|
AUDIOMIXER_API FSubmixEffectDynamicsProcessor();
|
|
|
|
AUDIOMIXER_API virtual ~FSubmixEffectDynamicsProcessor();
|
|
|
|
// Gets the effect's deviceId that owns it
|
|
AUDIOMIXER_API Audio::FDeviceId GetDeviceId() const;
|
|
|
|
// Called on an audio effect at initialization on audio thread before audio processing begins.
|
|
AUDIOMIXER_API virtual void Init(const FSoundEffectSubmixInitData& InInitData) override;
|
|
|
|
// Process the input block of audio. Called on audio render thread.
|
|
AUDIOMIXER_API virtual void OnProcessAudio(const FSoundEffectSubmixInputData& InData, FSoundEffectSubmixOutputData& OutData) override;
|
|
|
|
// Called when an audio effect preset is changed
|
|
AUDIOMIXER_API virtual void OnPresetChanged() override;
|
|
|
|
|
|
protected:
|
|
AUDIOMIXER_API Audio::FMixerDevice* GetMixerDevice();
|
|
|
|
AUDIOMIXER_API void ResetKey();
|
|
AUDIOMIXER_API void UpdateKeyFromSettings(const FSubmixEffectDynamicsProcessorSettings& InSettings);
|
|
AUDIOMIXER_API bool UpdateKeySourcePatch();
|
|
|
|
AUDIOMIXER_API void OnDeviceCreated(Audio::FDeviceId InDeviceId);
|
|
AUDIOMIXER_API void OnDeviceDestroyed(Audio::FDeviceId InDeviceId);
|
|
|
|
Audio::FAlignedFloatBuffer AudioExternal;
|
|
|
|
Audio::FDeviceId DeviceId = INDEX_NONE;
|
|
|
|
bool bBypass = false;
|
|
|
|
private:
|
|
FKeySource KeySource;
|
|
Audio::FDynamicsProcessor DynamicsProcessor;
|
|
|
|
FDelegateHandle DeviceCreatedHandle;
|
|
FDelegateHandle DeviceDestroyedHandle;
|
|
|
|
friend class USubmixEffectDynamicsProcessorPreset;
|
|
};
|
|
|
|
UCLASS(ClassGroup = AudioSourceEffect, meta = (BlueprintSpawnableComponent), MinimalAPI)
|
|
class USubmixEffectDynamicsProcessorPreset : public USoundEffectSubmixPreset
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
EFFECT_PRESET_METHODS(SubmixEffectDynamicsProcessor)
|
|
|
|
AUDIOMIXER_API virtual void OnInit() override;
|
|
|
|
AUDIOMIXER_API virtual void Serialize(FStructuredArchive::FRecord Record) override;
|
|
|
|
#if WITH_EDITOR
|
|
AUDIOMIXER_API virtual void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& InChainEvent) override;
|
|
#endif // WITH_EDITOR
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Audio|Effects")
|
|
AUDIOMIXER_API void ResetKey();
|
|
|
|
// Sets the source key input as the provided AudioBus' output. If no object is provided, key is set
|
|
// to effect's input.
|
|
UFUNCTION(BlueprintCallable, Category = "Audio|Effects")
|
|
AUDIOMIXER_API void SetAudioBus(UAudioBus* AudioBus);
|
|
|
|
// Sets the source key input as the provided Submix's output. If no object is provided, key is set
|
|
// to effect's input.
|
|
UFUNCTION(BlueprintCallable, Category = "Audio|Effects")
|
|
AUDIOMIXER_API void SetExternalSubmix(USoundSubmix* Submix);
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Audio|Effects")
|
|
AUDIOMIXER_API void SetSettings(const FSubmixEffectDynamicsProcessorSettings& Settings);
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SubmixEffectPreset, meta = (ShowOnlyInnerProperties))
|
|
FSubmixEffectDynamicsProcessorSettings Settings;
|
|
|
|
private:
|
|
void SetKey(ESubmixEffectDynamicsKeySource InKeySource, UObject* InObject, int32 InNumChannels = 0);
|
|
};
|