Files
UnrealEngine/Engine/Plugins/Experimental/MetasoundExperimental/Source/MetasoundExperimentalRuntime/Private/MetasoundCatWaveWriterNode.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

234 lines
7.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Audio/SimpleWaveWriter.h"
#include "ChannelAgnostic/ChannelAgnosticType.h"
#include "ChannelAgnostic/ChannelAgnosticTypeUtils.h"
#include "NumberedFileCache.h"
#include "HAL/FileManager.h"
#include "MetasoundBuildError.h"
#include "MetasoundChannelAgnosticType.h"
#include "MetasoundExecutableOperator.h"
#include "MetasoundNodeRegistrationMacro.h"
#include "MetasoundParamHelper.h"
#include "MetasoundPrimitives.h"
#include "MetasoundStandardNodesCategories.h"
#include "MetasoundStandardNodesNames.h"
#include "MetasoundVertex.h"
#include "Misc/Paths.h"
#include "Misc/ScopeLock.h"
#define LOCTEXT_NAMESPACE "MetasoundStandardNodes_CatWaveWriterNode"
namespace Metasound
{
namespace Experimental
{
namespace WaveWriterVertexNames
{
METASOUND_PARAM(InEnabledPin, "Enabled", "If this wave writer is enabled or not.");
METASOUND_PARAM(InFilenamePrefixPin, "Filename Prefix", "Filename Prefix of file you are writing.");
METASOUND_PARAM(InCatPin, "InputCat", "Channel Agnostic Input");
}
class FFileWriteError final : public FBuildErrorBase
{
public:
static const FName ErrorType;
virtual ~FFileWriteError() override = default;
FFileWriteError(const FNode& InNode, const FString& InFilename)
#if WITH_EDITOR
: FBuildErrorBase(ErrorType, METASOUND_LOCTEXT_FORMAT("MetasoundFileWriterErrorDescription", "File Writer Error while trying to write '{0}'", FText::FromString(InFilename)))
#else
: FBuildErrorBase(ErrorType, FText::GetEmpty())
#endif // WITH_EDITOR
{
AddNode(InNode);
}
};
const FName FFileWriteError::ErrorType = FName(TEXT("MetasoundFileWriterError"));
namespace WaveWriterOperatorPrivate
{
using namespace Audio;
// Need to keep this outside the template so there's only 1
TSharedPtr<FNumberedFileCache> GetNameCache()
{
static const TCHAR* WaveExt = TEXT(".wav");
// Build cache of numbered files (do this once only).
static TSharedPtr<FNumberedFileCache> NumberedFileCacheSP = MakeShared<FNumberedFileCache>(*FPaths::AudioCaptureDir(), WaveExt, IFileManager::Get());
return NumberedFileCacheSP;
}
static const FString& GetDefaultFileName()
{
static const FString DefaultFileName = TEXT("Output");
return DefaultFileName;
}
}
class FCatWaveWriterOperator final : public TExecutableOperator<FCatWaveWriterOperator>
{
public:
FCatWaveWriterOperator(const FBuildOperatorParams& InParams, FChannelAgnosticTypeReadRef&& InAudioBuffers, FBoolReadRef&& InEnabled, const TSharedPtr<FNumberedFileCache, ESPMode::ThreadSafe>& InNumberedFileCache, FStringReadRef&& InFilenamePrefix)
: AudioInputs{MoveTemp(InAudioBuffers)}
, Enabled{MoveTemp(InEnabled)}
, NumberedFileCacheSP{InNumberedFileCache}
, FileNamePrefix{MoveTemp(InFilenamePrefix)}
, SampleRate{InParams.OperatorSettings.GetSampleRate()}
, NumInputFrames{InParams.OperatorSettings.GetNumFramesPerBlock()}
{
// Now we have an input, we can ask how many channel it has.
NumInputChannels = AudioInputs->NumChannels();
InterleaveBuffer.SetNumZeroed(NumInputChannels * NumInputFrames);
}
virtual void BindInputs(FInputVertexInterfaceData& InOutVertexData) override {}
virtual void BindOutputs(FOutputVertexInterfaceData&) override {}
static const FVertexInterface& DeclareVertexInterface()
{
auto CreateDefaultInterface = []()-> FVertexInterface
{
using namespace WaveWriterOperatorPrivate;
using namespace WaveWriterVertexNames;
// inputs
const FInputVertexInterface InputInterface(
TInputDataVertex<FString>(METASOUND_GET_PARAM_NAME_AND_METADATA(InFilenamePrefixPin), GetDefaultFileName()),
TInputDataVertex<bool>(METASOUND_GET_PARAM_NAME_AND_METADATA(InEnabledPin), true),
TInputDataVertex<FChannelAgnosticType>(METASOUND_GET_PARAM_NAME_AND_METADATA(InCatPin))
);
const FOutputVertexInterface OutputInterface;
return FVertexInterface(InputInterface, OutputInterface);
};
static const FVertexInterface DefaultInterface = CreateDefaultInterface();
return DefaultInterface;
}
static const FNodeClassMetadata& GetNodeInfo()
{
static const FNodeClassMetadata Metadata = CreateNodeClassMetadata
(
FName(TEXT("Cat Wave Writer")),
METASOUND_LOCTEXT("Metasound_CatWaveWriterNodeMultiChannelDisplayName", "Wave Writer Channel Agnostic"),
METASOUND_LOCTEXT("Metasound_CatWaveWriterNodeMultiDescription", "Write a CAT audio signal to disk"),
DeclareVertexInterface()
);
return Metadata;
}
static TUniquePtr<IOperator> CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults);
void Execute()
{
// Enabled and wasn't before? Enable.
if (!bIsEnabled && *Enabled)
{
Enable();
}
// Disabled but currently Enabled? Disable.
else if (bIsEnabled && !*Enabled)
{
Disable();
}
// If we have a valid writer and enabled.
if (Writer && *Enabled && NumInputChannels > 0)
{
Audio::FCatUtils::Interleave(*AudioInputs, InterleaveBuffer);
Writer->Write(InterleaveBuffer);
}
}
void Reset(const IOperator::FResetParams& InParams)
{
if (bIsEnabled)
{
Disable();
}
}
protected:
static FNodeClassMetadata CreateNodeClassMetadata(const FName& InOperatorName, const FText& InDisplayName, const FText& InDescription, const FVertexInterface& InDefaultInterface)
{
FNodeClassMetadata Metadata
{
FNodeClassName { StandardNodes::Namespace, InOperatorName, StandardNodes::AudioVariant },
1, // Major Version
1, // Minor Version
InDisplayName,
InDescription,
PluginAuthor,
PluginNodeMissingPrompt,
InDefaultInterface,
{ NodeCategories::Io },
{ METASOUND_LOCTEXT("Metasound_AudioMixerKeyword", "Writer") },
FNodeDisplayStyle{}
};
return Metadata;
}
void Enable()
{
if (ensure(!bIsEnabled) && NumInputChannels > 0)
{
bIsEnabled = true;
const FString Filename = NumberedFileCacheSP->GenerateNextNumberedFilename(*FileNamePrefix);
if (TUniquePtr<FArchive> Stream {IFileManager::Get().CreateFileWriter(*Filename, IO_WRITE) }; Stream.IsValid() )
{
Writer = MakeUnique<Audio::FSimpleWaveWriter>(MoveTemp(Stream), SampleRate, NumInputChannels, true);
}
}
}
void Disable()
{
if (ensure(bIsEnabled))
{
bIsEnabled = false;
Writer.Reset();
NumInputChannels = 0;
}
}
FChannelAgnosticTypeReadRef AudioInputs;
TArray<float> InterleaveBuffer;
FBoolReadRef Enabled;
TUniquePtr<Audio::FSimpleWaveWriter> Writer;
TSharedPtr<FNumberedFileCache> NumberedFileCacheSP;
FStringReadRef FileNamePrefix;
float SampleRate = 0.f;
int32 NumInputChannels = 0;
int32 NumInputFrames = 0;
bool bIsEnabled = false;
};
TUniquePtr<IOperator> FCatWaveWriterOperator::CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults)
{
using namespace WaveWriterOperatorPrivate;
using namespace WaveWriterVertexNames;
const FOperatorSettings& Settings = InParams.OperatorSettings;
const FInputVertexInterfaceData& InputData = InParams.InputData;
return MakeUnique<FCatWaveWriterOperator>(
InParams,
InputData.GetOrCreateDefaultDataReadReference<FChannelAgnosticType>(METASOUND_GET_PARAM_NAME(InCatPin), Settings),
InputData.GetOrCreateDefaultDataReadReference<bool>(METASOUND_GET_PARAM_NAME(InEnabledPin), Settings),
GetNameCache(),
InputData.GetOrCreateDefaultDataReadReference<FString>(METASOUND_GET_PARAM_NAME(InFilenamePrefixPin), Settings)
);
}
using FCatWaveWriterNode = TNodeFacade<FCatWaveWriterOperator>;
METASOUND_REGISTER_NODE(FCatWaveWriterNode);
}// namespace Experimental
} // namespace Metasounds
#undef LOCTEXT_NAMESPACE