2022-11-14 14:44:30 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2024-08-14 10:45:44 -04:00
# include "MetasoundAudioBusWriterNode.h"
2023-04-21 20:49:41 -04:00
# include "AudioMixerDevice.h"
2023-04-21 14:55:25 -04:00
# include "AudioBusSubsystem.h"
# include "AudioDevice.h"
2022-11-14 14:44:30 -05:00
# include "Internationalization/Text.h"
# include "MediaPacket.h"
# include "MetasoundAudioBuffer.h"
# include "MetasoundAudioBus.h"
# include "MetasoundExecutableOperator.h"
# include "MetasoundFacade.h"
# include "MetasoundNodeRegistrationMacro.h"
# include "MetasoundParamHelper.h"
# include "MetasoundStandardNodesCategories.h"
# define LOCTEXT_NAMESPACE "MetasoundAudioBusWriterNode"
namespace Metasound
{
namespace AudioBusWriterNode
{
2024-08-14 10:45:44 -04:00
namespace Inputs
{
DEFINE_METASOUND_PARAM ( AudioBus , " Audio Bus " , " Audio Bus Asset. " ) ;
DEFINE_METASOUND_PARAM ( Audio , " In {0} " , " Audio input for channel {0}. " ) ;
}
int32 GetCurrentMajorVersion ( )
{
return 1 ;
}
2022-11-14 14:44:30 -05:00
}
2024-05-30 10:27:00 -04:00
int32 AudioBusWriterNodeInitialNumBlocks ( int32 BlockSizeFrames , int32 AudioMixerOutputFrames )
{
// One less block is required because the metasound will write the final block.
int32 MaxSizeFrames = FMath : : Max ( AudioMixerOutputFrames , BlockSizeFrames ) , MinSizeFrames = FMath : : Min ( AudioMixerOutputFrames , BlockSizeFrames ) ;
return FMath : : DivideAndRoundUp ( MaxSizeFrames , MinSizeFrames ) - 1 ;
}
2022-11-14 14:44:30 -05:00
template < uint32 NumChannels >
class TAudioBusWriterOperator : public TExecutableOperator < TAudioBusWriterOperator < NumChannels > >
{
public :
static const FNodeClassMetadata & GetNodeInfo ( )
{
auto InitNodeInfo = [ ] ( ) - > FNodeClassMetadata
{
FText NodeDisplayName = METASOUND_LOCTEXT_FORMAT ( " AudioBusWriterDisplayNamePattern " , " Audio Bus Writer ({0}) " , NumChannels ) ;
FNodeClassMetadata Info ;
2024-08-14 10:45:44 -04:00
Info . ClassName = AudioBusWriterNode : : GetClassName < NumChannels > ( ) ;
Info . MajorVersion = AudioBusWriterNode : : GetCurrentMajorVersion ( ) ;
2022-11-14 14:44:30 -05:00
Info . MinorVersion = 0 ;
Info . DisplayName = NodeDisplayName ;
Info . Description = METASOUND_LOCTEXT ( " AudioBusWriter_Description " , " Sends audio data to the audio bus asset. " ) ;
Info . Author = PluginAuthor ;
Info . PromptIfMissing = PluginNodeMissingPrompt ;
Info . DefaultInterface = GetVertexInterface ( ) ;
Info . CategoryHierarchy . Emplace ( NodeCategories : : Io ) ;
return Info ;
} ;
static const FNodeClassMetadata Info = InitNodeInfo ( ) ;
return Info ;
}
static const FVertexInterface & GetVertexInterface ( )
{
using namespace AudioBusWriterNode ;
auto CreateVertexInterface = [ ] ( ) - > FVertexInterface
{
FInputVertexInterface InputInterface ;
2024-08-14 10:45:44 -04:00
InputInterface . Add ( TInputDataVertex < FAudioBusAsset > ( METASOUND_GET_PARAM_NAME_AND_METADATA ( Inputs : : AudioBus ) ) ) ;
2022-11-14 14:44:30 -05:00
for ( uint32 i = 0 ; i < NumChannels ; + + i )
{
2024-08-14 10:45:44 -04:00
InputInterface . Add ( TInputDataVertex < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_WITH_INDEX_AND_METADATA ( Inputs : : Audio , i ) ) ) ;
2022-11-14 14:44:30 -05:00
}
FOutputVertexInterface OutputInterface ;
return FVertexInterface ( InputInterface , OutputInterface ) ;
} ;
static const FVertexInterface Interface = CreateVertexInterface ( ) ;
return Interface ;
}
2023-10-13 19:29:51 -04:00
static TUniquePtr < IOperator > CreateOperator ( const FBuildOperatorParams & InParams , FBuildResults & OutResults )
2022-11-14 14:44:30 -05:00
{
using namespace Frontend ;
2023-04-27 18:49:33 -04:00
using namespace AudioBusWriterNode ;
2023-05-09 17:27:12 -04:00
2023-10-13 19:29:51 -04:00
const FInputVertexInterfaceData & InputData = InParams . InputData ;
2022-11-14 14:44:30 -05:00
2023-05-09 17:27:12 -04:00
bool bHasEnvironmentVars = InParams . Environment . Contains < Audio : : FDeviceId > ( SourceInterface : : Environment : : DeviceID ) ;
bHasEnvironmentVars & = InParams . Environment . Contains < int32 > ( SourceInterface : : Environment : : AudioMixerNumOutputFrames ) ;
2024-05-30 10:27:00 -04:00
bHasEnvironmentVars & = InParams . Environment . Contains < uint64 > ( SourceInterface : : Environment : : TransmitterID ) ;
2023-05-09 17:27:12 -04:00
2023-01-25 20:41:44 -05:00
if ( bHasEnvironmentVars )
2022-11-14 14:44:30 -05:00
{
2024-08-14 10:45:44 -04:00
FAudioBusAssetReadRef AudioBusIn = InputData . GetOrConstructDataReadReference < FAudioBusAsset > ( METASOUND_GET_PARAM_NAME ( Inputs : : AudioBus ) ) ;
2023-01-25 20:41:44 -05:00
TArray < FAudioBufferReadRef > AudioInputs ;
for ( int32 ChannelIndex = 0 ; ChannelIndex < NumChannels ; + + ChannelIndex )
{
2024-08-14 10:45:44 -04:00
AudioInputs . Add ( InputData . GetOrConstructDataReadReference < FAudioBuffer > ( METASOUND_GET_PARAM_NAME_WITH_INDEX ( Inputs : : Audio , ChannelIndex ) , InParams . OperatorSettings ) ) ;
2023-01-25 20:41:44 -05:00
}
2023-10-16 14:11:32 -04:00
FString GraphName ;
if ( InParams . Environment . Contains < FString > ( SourceInterface : : Environment : : GraphName ) )
{
GraphName = InParams . Environment . GetValue < FString > ( SourceInterface : : Environment : : GraphName ) ;
}
else
{
GraphName = TEXT ( " <Unknown> " ) ;
}
return MakeUnique < TAudioBusWriterOperator < NumChannels > > ( InParams , MoveTemp ( AudioBusIn ) , MoveTemp ( AudioInputs ) , MoveTemp ( GraphName ) ) ;
2023-01-25 20:41:44 -05:00
}
else
{
2024-05-30 10:27:00 -04:00
UE_LOG ( LogMetaSound , Warning , TEXT ( " Audio bus writer node requires audio device ID '%s', audio mixer num output frames '%s' and transmitter id '%s' environment variables " )
, * SourceInterface : : Environment : : DeviceID . ToString ( ) , * SourceInterface : : Environment : : AudioMixerNumOutputFrames . ToString ( ) , * SourceInterface : : Environment : : TransmitterID . ToString ( ) ) ;
2023-01-25 20:41:44 -05:00
return nullptr ;
}
2022-11-14 14:44:30 -05:00
}
2023-10-16 14:42:40 -04:00
TAudioBusWriterOperator ( const FBuildOperatorParams & InParams , FAudioBusAssetReadRef InAudioBusAsset , TArray < FAudioBufferReadRef > InAudioInputs , FString InGraphName )
: AudioBusAsset ( MoveTemp ( InAudioBusAsset ) )
, AudioInputs ( MoveTemp ( InAudioInputs ) )
2022-11-14 14:44:30 -05:00
{
2023-05-09 17:27:12 -04:00
Reset ( InParams ) ;
}
2023-06-07 12:51:55 -04:00
void CreatePatchInput ( )
{
const FAudioBusProxyPtr & AudioBusProxy = AudioBusAsset - > GetAudioBusProxy ( ) ;
if ( AudioBusProxy . IsValid ( ) )
{
2024-05-30 10:27:00 -04:00
if ( AudioBusProxy - > NumChannels < = 0 )
{
UE_LOG ( LogMetaSound , Warning , TEXT ( " AudioBusProxy is invalid (NumChannels = %i). " ) , AudioBusProxy - > NumChannels ) ;
return ;
}
UAudioBusSubsystem * AudioBusSubsystem = nullptr ;
2023-06-07 12:51:55 -04:00
if ( FAudioDeviceManager * ADM = FAudioDeviceManager : : Get ( ) )
{
if ( FAudioDevice * AudioDevice = ADM - > GetAudioDeviceRaw ( AudioDeviceId ) )
{
2024-05-30 10:27:00 -04:00
AudioBusSubsystem = AudioDevice - > GetSubsystem < UAudioBusSubsystem > ( ) ;
2023-06-07 12:51:55 -04:00
check ( AudioBusSubsystem ) ;
}
}
2024-05-30 19:35:23 -04:00
if ( ! AudioBusSubsystem )
{
return ;
}
2024-05-30 10:27:00 -04:00
2024-06-13 13:14:05 -04:00
AudioBusChannels = FMath : : Min ( uint32 ( AudioBusProxy - > NumChannels ) , uint32 ( EAudioBusChannels : : MaxChannelCount ) ) ;
2024-05-30 10:27:00 -04:00
AudioBusId = AudioBusProxy - > AudioBusId ;
Audio : : FAudioBusKey AudioBusKey ( AudioBusId ) ;
AudioBusSubsystem - > StartAudioBus ( AudioBusKey , AudioBusChannels , false ) ;
AudioBusPatchInput = AudioBusSubsystem - > AddPatchInputForSoundAndAudioBus ( InstanceID , AudioBusKey , BlockSizeFrames , int32 ( AudioBusChannels ) ) ;
int32 NumBlocksToPush = InitialNumBlocks ( ) ;
if ( NumBlocksToPush > 0 )
{
AudioBusPatchInput . PushAudio ( nullptr , NumBlocksToNumSamples ( NumBlocksToPush ) ) ;
}
2024-06-13 13:14:05 -04:00
// Allocate and fill the interleaved buffer with silence,
// in case it contains more channels than the node supports.
2024-05-30 10:27:00 -04:00
InterleavedBuffer . Reset ( ) ;
2024-06-13 13:14:05 -04:00
InterleavedBuffer . AddZeroed ( NumBlocksToNumSamples ( 1 ) ) ;
2023-06-07 12:51:55 -04:00
}
}
2023-05-09 17:27:12 -04:00
void Reset ( const IOperator : : FResetParams & InParams )
{
using namespace Frontend ;
using namespace AudioBusWriterNode ;
2024-05-30 10:27:00 -04:00
InterleavedBuffer . Reset ( ) ;
AudioMixerOutputFrames = INDEX_NONE ;
AudioDeviceId = INDEX_NONE ;
SampleRate = InParams . OperatorSettings . GetSampleRate ( ) ;
AudioBusPatchInput . Reset ( ) ;
AudioBusChannels = INDEX_NONE ;
AudioBusId = 0 ;
InstanceID = 0 ;
BlockSizeFrames = InParams . OperatorSettings . GetNumFramesPerBlock ( ) ;
bWasUnderrunReported = false ;
2023-05-09 17:27:12 -04:00
bool bHasEnvironmentVars = InParams . Environment . Contains < Audio : : FDeviceId > ( SourceInterface : : Environment : : DeviceID ) ;
bHasEnvironmentVars & = InParams . Environment . Contains < int32 > ( SourceInterface : : Environment : : AudioMixerNumOutputFrames ) ;
2024-05-30 10:27:00 -04:00
bHasEnvironmentVars & = InParams . Environment . Contains < uint64 > ( SourceInterface : : Environment : : TransmitterID ) ;
2023-05-09 17:27:12 -04:00
if ( bHasEnvironmentVars )
{
AudioDeviceId = InParams . Environment . GetValue < Audio : : FDeviceId > ( SourceInterface : : Environment : : DeviceID ) ;
AudioMixerOutputFrames = InParams . Environment . GetValue < int32 > ( SourceInterface : : Environment : : AudioMixerNumOutputFrames ) ;
2024-05-30 10:27:00 -04:00
InstanceID = InParams . Environment . GetValue < uint64 > ( SourceInterface : : Environment : : TransmitterID ) ;
2023-05-09 17:27:12 -04:00
}
else
{
2024-05-30 10:27:00 -04:00
UE_LOG ( LogMetaSound , Warning , TEXT ( " Audio bus writer node requires audio device ID '%s', audio mixer num output frames '%s' and transmitter id '%s' environment variables " )
, * SourceInterface : : Environment : : DeviceID . ToString ( ) , * SourceInterface : : Environment : : AudioMixerNumOutputFrames . ToString ( ) , * SourceInterface : : Environment : : TransmitterID . ToString ( ) ) ;
2023-05-09 17:27:12 -04:00
}
2022-11-14 14:44:30 -05:00
}
[Metasound Bind] Fixup for bind issues caught by new automated tests.
#jira UE-187406, UE-187390, UE-187404, UE-187403, UE-187405, UE-187392, UE-187391, UE-187395, UE-187399, UE-187398, UE-187389, UE-187393, UE-187394, UE-187396, UE-187397, UE-187401
#rb phil.popp
[CL 26130674 by maxwell hayes in ue5-main branch]
2023-06-20 15:08:54 -04:00
virtual void BindInputs ( FInputVertexInterfaceData & InOutVertexData ) override
2022-11-14 14:44:30 -05:00
{
using namespace AudioBusWriterNode ;
2024-08-14 10:45:44 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME ( Inputs : : AudioBus ) , AudioBusAsset ) ;
2022-11-14 14:44:30 -05:00
for ( int32 ChannelIndex = 0 ; ChannelIndex < NumChannels ; + + ChannelIndex )
{
2024-08-14 10:45:44 -04:00
InOutVertexData . BindReadVertex ( METASOUND_GET_PARAM_NAME_WITH_INDEX ( Inputs : : Audio , ChannelIndex ) , AudioInputs [ ChannelIndex ] ) ;
2022-11-14 14:44:30 -05:00
}
[Metasound Bind] Fixup for bind issues caught by new automated tests.
#jira UE-187406, UE-187390, UE-187404, UE-187403, UE-187405, UE-187392, UE-187391, UE-187395, UE-187399, UE-187398, UE-187389, UE-187393, UE-187394, UE-187396, UE-187397, UE-187401
#rb phil.popp
[CL 26130674 by maxwell hayes in ue5-main branch]
2023-06-20 15:08:54 -04:00
}
2022-11-14 14:44:30 -05:00
[Metasound Bind] Fixup for bind issues caught by new automated tests.
#jira UE-187406, UE-187390, UE-187404, UE-187403, UE-187405, UE-187392, UE-187391, UE-187395, UE-187399, UE-187398, UE-187389, UE-187393, UE-187394, UE-187396, UE-187397, UE-187401
#rb phil.popp
[CL 26130674 by maxwell hayes in ue5-main branch]
2023-06-20 15:08:54 -04:00
virtual void BindOutputs ( FOutputVertexInterfaceData & InOutVertexData ) override
{
}
virtual FDataReferenceCollection GetInputs ( ) const override
{
// This should never be called. Bind(...) is called instead. This method
// exists as a stop-gap until the API can be deprecated and removed.
checkNoEntry ( ) ;
return { } ;
2022-11-14 14:44:30 -05:00
}
virtual FDataReferenceCollection GetOutputs ( ) const override
{
[Metasound Bind] Fixup for bind issues caught by new automated tests.
#jira UE-187406, UE-187390, UE-187404, UE-187403, UE-187405, UE-187392, UE-187391, UE-187395, UE-187399, UE-187398, UE-187389, UE-187393, UE-187394, UE-187396, UE-187397, UE-187401
#rb phil.popp
[CL 26130674 by maxwell hayes in ue5-main branch]
2023-06-20 15:08:54 -04:00
// This should never be called. Bind(...) is called instead. This method
// exists as a stop-gap until the API can be deprecated and removed.
checkNoEntry ( ) ;
return { } ;
2022-11-14 14:44:30 -05:00
}
void Execute ( )
{
2023-06-07 12:51:55 -04:00
const FAudioBusProxyPtr & BusProxy = AudioBusAsset - > GetAudioBusProxy ( ) ;
if ( BusProxy . IsValid ( ) & & BusProxy - > AudioBusId ! = AudioBusId )
{
2024-05-30 10:27:00 -04:00
InterleavedBuffer . Reset ( ) ;
2023-06-07 12:51:55 -04:00
}
2024-05-30 10:27:00 -04:00
if ( InterleavedBuffer . IsEmpty ( ) )
2023-06-07 12:51:55 -04:00
{
// if environment vars & a valid audio bus have been set since starting, try to create the patch now
if ( SampleRate > 0.f & & BusProxy . IsValid ( ) )
{
CreatePatchInput ( ) ;
}
}
2024-05-30 10:27:00 -04:00
if ( InterleavedBuffer . IsEmpty ( ) )
2022-11-14 14:44:30 -05:00
{
return ;
}
// Retrieve input and interleaved buffer pointers
const float * AudioInputBufferPtrs [ NumChannels ] ;
2022-11-29 12:35:48 -05:00
for ( uint32 ChannelIndex = 0 ; ChannelIndex < NumChannels ; + + ChannelIndex )
2022-11-14 14:44:30 -05:00
{
AudioInputBufferPtrs [ ChannelIndex ] = AudioInputs [ ChannelIndex ] - > GetData ( ) ;
}
2024-05-30 10:27:00 -04:00
float * InterleavedBufferPtr = InterleavedBuffer . GetData ( ) ;
2022-11-14 14:44:30 -05:00
2022-11-29 12:35:48 -05:00
if ( AudioBusChannels = = 1 )
2022-11-14 14:44:30 -05:00
{
2022-11-29 12:35:48 -05:00
FMemory : : Memcpy ( InterleavedBufferPtr , AudioInputBufferPtrs [ 0 ] , BlockSizeFrames * sizeof ( float ) ) ;
}
else
{
// Interleave the inputs
// Writing the channels of the interleaved buffer sequentially should improve
// cache utilization compared to writing each input's frames sequentially.
// There is more likely to be a cache line for each buffer than for the
// entirety of the interleaved buffer.
uint32 MinChannels = FMath : : Min ( AudioBusChannels , NumChannels ) ;
for ( int32 FrameIndex = 0 ; FrameIndex < BlockSizeFrames ; + + FrameIndex )
2022-11-14 14:44:30 -05:00
{
2024-06-13 13:14:05 -04:00
// Fill as many channels in the interleaved buffer as possible,
// given the number of available audio buffers.
2022-11-29 12:35:48 -05:00
for ( uint32 ChannelIndex = 0 ; ChannelIndex < MinChannels ; + + ChannelIndex )
{
InterleavedBufferPtr [ ChannelIndex ] = * AudioInputBufferPtrs [ ChannelIndex ] + + ;
}
2024-06-13 13:14:05 -04:00
// The interleaved buffer has as many channels as the assigned audio bus.
2022-11-29 12:35:48 -05:00
InterleavedBufferPtr + = AudioBusChannels ;
2022-11-14 14:44:30 -05:00
}
}
// Pushes the interleaved data to the audio bus
2023-09-29 14:26:52 -04:00
const int32 SamplesPushed = AudioBusPatchInput . PushAudio ( InterleavedBuffer . GetData ( ) , InterleavedBuffer . Num ( ) ) ;
if ( SamplesPushed < InterleavedBuffer . Num ( ) & & ! bWasUnderrunReported )
2022-11-29 12:35:48 -05:00
{
UE_LOG ( LogMetaSound , Warning , TEXT ( " Underrun detected in audio bus writer node. " ) ) ;
2023-09-29 14:26:52 -04:00
bWasUnderrunReported = true ;
2022-11-29 12:35:48 -05:00
}
2022-11-14 14:44:30 -05:00
}
private :
2023-10-16 14:11:32 -04:00
int32 InitialNumBlocks ( ) const
{
2024-05-30 10:27:00 -04:00
return AudioBusWriterNodeInitialNumBlocks ( BlockSizeFrames , AudioMixerOutputFrames ) ;
2023-10-16 14:11:32 -04:00
}
2023-08-31 11:23:00 -04:00
int32 NumBlocksToNumSamples ( int32 NumBlocks ) const
{
return NumBlocks * BlockSizeFrames * AudioBusChannels ;
}
2022-11-14 14:44:30 -05:00
FAudioBusAssetReadRef AudioBusAsset ;
TArray < FAudioBufferReadRef > AudioInputs ;
TArray < float > InterleavedBuffer ;
int32 AudioMixerOutputFrames = INDEX_NONE ;
Audio : : FDeviceId AudioDeviceId = INDEX_NONE ;
float SampleRate = 0.0f ;
Audio : : FPatchInput AudioBusPatchInput ;
2024-05-30 10:27:00 -04:00
uint64 InstanceID = 0 ;
2022-11-14 14:44:30 -05:00
uint32 AudioBusChannels = INDEX_NONE ;
2023-06-07 12:51:55 -04:00
uint32 AudioBusId = 0 ;
2022-11-14 14:44:30 -05:00
int32 BlockSizeFrames = 0 ;
2024-05-30 10:27:00 -04:00
bool bWasUnderrunReported = false ;
2022-11-14 14:44:30 -05:00
} ;
template < uint32 NumChannels >
class TAudioBusWriterNode : public FNodeFacade
{
public :
TAudioBusWriterNode ( const FNodeInitData & InitData )
: FNodeFacade ( InitData . InstanceName , InitData . InstanceID , TFacadeOperatorClass < TAudioBusWriterOperator < NumChannels > > ( ) )
{
}
} ;
# define REGISTER_AUDIO_BUS_WRITER_NODE(ChannelCount) \
using FAudioBusWriterNode_ # # ChannelCount = TAudioBusWriterNode < ChannelCount > ; \
METASOUND_REGISTER_NODE ( FAudioBusWriterNode_ # # ChannelCount ) \
REGISTER_AUDIO_BUS_WRITER_NODE ( 1 ) ;
REGISTER_AUDIO_BUS_WRITER_NODE ( 2 ) ;
2022-11-29 12:35:48 -05:00
REGISTER_AUDIO_BUS_WRITER_NODE ( 4 ) ;
REGISTER_AUDIO_BUS_WRITER_NODE ( 6 ) ;
REGISTER_AUDIO_BUS_WRITER_NODE ( 8 ) ;
2022-11-14 14:44:30 -05:00
}
# undef LOCTEXT_NAMESPACE