2020-07-28 11:45:27 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-01-27 15:54:01 -04:00
# include "AudioResampler.h"
2021-03-25 23:30:50 -04:00
# include "CoreMinimal.h"
# include "DSP/BufferVectorOperations.h"
2021-01-27 15:54:01 -04:00
# include "MetasoundBuildError.h"
2020-07-28 11:45:27 -04:00
# include "MetasoundExecutableOperator.h"
2021-03-25 23:30:50 -04:00
# include "MetasoundLog.h"
2020-08-11 01:36:57 -04:00
# include "MetasoundNodeRegistrationMacro.h"
2020-07-28 11:45:27 -04:00
# include "MetasoundPrimitives.h"
# include "MetasoundWave.h"
2021-01-27 15:54:01 -04:00
# include "MetasoundTrigger.h"
2021-02-01 21:03:37 -04:00
# include "MetasoundEngineNodesNames.h"
2021-09-13 14:14:37 -04:00
# include "MetasoundVertex.h"
2021-01-27 15:54:01 -04:00
2020-07-28 11:45:27 -04:00
# define LOCTEXT_NAMESPACE "MetasoundWaveNode"
2021-09-13 14:14:37 -04:00
2020-07-28 11:45:27 -04:00
namespace Metasound
{
2021-03-31 04:09:57 -04:00
namespace WavePlayerVertexNames
{
static const TCHAR * InputTriggerPlayName = TEXT ( " Play " ) ;
static const TCHAR * InputTriggerStopName = TEXT ( " Stop " ) ;
static const TCHAR * InputWaveAssetName = TEXT ( " Wave Asset " ) ;
static const TCHAR * InputStartTimeName = TEXT ( " Start Time " ) ;
static const TCHAR * InputPitchShiftName = TEXT ( " Pitch Shift " ) ;
static const TCHAR * InputLoopName = TEXT ( " Loop " ) ;
static const TCHAR * InputLoopStartName = TEXT ( " Loop Start " ) ;
static const TCHAR * InputLoopDurationName = TEXT ( " Loop Duration " ) ;
//static const TCHAR* InputStartTimeAsPercentName = TEXT("Start/Loop Times As Percent");
static const TCHAR * OutputTriggerOnPlayName = TEXT ( " On Play " ) ;
2021-04-03 19:59:23 -04:00
static const TCHAR * OutputTriggerOnDoneName = TEXT ( " On Finished " ) ;
static const TCHAR * OutputTriggerOnNearlyDoneName = TEXT ( " On Nearly Finished " ) ;
2021-03-31 04:09:57 -04:00
static const TCHAR * OutputTriggerOnLoopedName = TEXT ( " On Looped " ) ;
static const TCHAR * OutputTriggerOnCuePointName = TEXT ( " On Cue Point " ) ;
static const TCHAR * OutputCuePointIDName = TEXT ( " Cue Point ID " ) ;
static const TCHAR * OutputCuePointLabelName = TEXT ( " Cue Point Label " ) ;
static const TCHAR * OutputLoopPercentName = TEXT ( " Loop Percent " ) ;
static const TCHAR * OutputPlaybackLocationName = TEXT ( " Playback Location " ) ;
static const TCHAR * OutputAudioLeftName = TEXT ( " Out Left " ) ;
static const TCHAR * OutputAudioRightName = TEXT ( " Out Right " ) ;
static FText InputTriggerPlayTT = LOCTEXT ( " PlayTT " , " Play the wave player. " ) ;
static FText InputTriggerStopTT = LOCTEXT ( " StopTT " , " Stop the wave player. " ) ;
static FText InputWaveAssetTT = LOCTEXT ( " WaveTT " , " The wave asset to be real-time decoded. " ) ;
static FText InputStartTimeTT = LOCTEXT ( " StartTimeTT " , " Time into the wave asset to start (seek) the wave asset. For real-time decoding, the wave asset must be set to seekable!) " ) ;
static FText InputPitchShiftTT = LOCTEXT ( " PitchShiftTT " , " The pitch shift to use for the wave asset in semitones. " ) ;
static FText InputLoopTT = LOCTEXT ( " LoopTT " , " Whether or not to loop between the start and specified end times. " ) ;
static FText InputLoopStartTT = LOCTEXT ( " LoopStartTT " , " When to start the loop. Will be a percentage if \" Start Time As Percent \" is true. " ) ;
static FText InputLoopDurationTT = LOCTEXT ( " LoopDurationTT " , " The duration of the loop when wave player is enabled for looping. A value of -1.0 will loop the whole wave asset. " ) ;
//static FText InputStartTimeAsPercentTT = LOCTEXT("StartTimeAsPercentTT", "Whether to treat the start time and loop start times as a percentage of total wave duration vs seconds (default).");
static FText OutputTriggerOnPlayTT = LOCTEXT ( " OnPlayTT " , " Triggers when Play is triggered. " ) ;
static FText OutputTriggerOnDoneTT = LOCTEXT ( " OnDoneTT " , " Triggers when the wave played has finished playing. " ) ;
static FText OutputTriggerOnNearlyDoneTT = LOCTEXT ( " OnNearlyDoneTT " , " Triggers when the wave played has almost finished playing (the block before it finishes). Allows time for logic to trigger different variations to play seamlessly. " ) ;
static FText OutputTriggerOnLoopedTT = LOCTEXT ( " OnLoopedTT " , " Triggers when the wave player has looped. " ) ;
static FText OutputTriggerOnCuePointTT = LOCTEXT ( " OnCuePointTT " , " Triggers when a wave cue point was hit during playback. " ) ;
static FText OutputCuePointIDTT = LOCTEXT ( " CuePointIDTT " , " The cue point ID that was triggered. " ) ;
static FText OutputCuePointLabelTT = LOCTEXT ( " CuePointLabelTT " , " The cue point label that was triggered (if there was a label parsed in the imported .wav file). " ) ;
static FText OutputLoopPercentTT = LOCTEXT ( " LoopPercentTT " , " Returns the current loop percent if looping is enabled. " ) ;
static FText OutputPlaybackLocationTT = LOCTEXT ( " PlaybackLocationTT " , " Returns the absolute position of the wave playback as a precentage of wave duration. " ) ;
static FText OutputAudioLeftNameTT = LOCTEXT ( " AudioLeftTT " , " The left channel audio output. Mono wave assets will be upmixed to dual stereo. " ) ;
static FText OutputAudioRightNameTT = LOCTEXT ( " AudioRightTT " , " The right channel audio output. Mono wave assets will be upmixed to dual stereo. " ) ;
}
2021-03-25 23:30:50 -04:00
class FWavePlayerNode : public FNode
2020-07-30 19:14:41 -04:00
{
2021-03-25 23:30:50 -04:00
class FOperatorFactory : public IOperatorFactory
2020-11-04 14:26:37 -04:00
{
2021-03-25 23:30:50 -04:00
virtual TUniquePtr < IOperator > CreateOperator ( const FCreateOperatorParams & InParams , FBuildErrorArray & OutErrors ) override ;
} ;
2020-11-04 14:26:37 -04:00
2021-03-25 23:30:50 -04:00
public :
static FVertexInterface DeclareVertexInterface ( ) ;
static const FNodeClassMetadata & GetNodeInfo ( ) ;
2020-11-04 14:26:37 -04:00
2021-09-13 14:14:37 -04:00
FWavePlayerNode ( const FVertexName & InName , const FGuid & InInstanceID ) ;
2021-03-25 23:30:50 -04:00
FWavePlayerNode ( const FNodeInitData & InInitData ) ;
virtual ~ FWavePlayerNode ( ) = default ;
virtual FOperatorFactorySharedRef GetDefaultOperatorFactory ( ) const override ;
virtual const FVertexInterface & GetVertexInterface ( ) const override ;
virtual bool SetVertexInterface ( const FVertexInterface & InInterface ) override ;
virtual bool IsVertexInterfaceSupported ( const FVertexInterface & InInterface ) const override ;
private :
FOperatorFactorySharedRef Factory ;
FVertexInterface Interface ;
2020-07-30 19:14:41 -04:00
} ;
2020-11-04 14:26:37 -04:00
2021-03-31 04:09:57 -04:00
struct FWavePlayerOpArgs
{
FOperatorSettings Settings ;
FTriggerReadRef PlayTrigger ;
FTriggerReadRef StopTrigger ;
FWaveAssetReadRef WaveAsset ;
FTimeReadRef StartTime ;
FFloatReadRef PitchShift ;
FBoolReadRef bLoop ;
FTimeReadRef LoopStartTime ;
FTimeReadRef LoopDurationSeconds ;
//FBoolReadRef bStartTimeAsPercentage;
} ;
2021-03-25 23:30:50 -04:00
2020-07-28 11:45:27 -04:00
class FWavePlayerOperator : public TExecutableOperator < FWavePlayerOperator >
2021-03-31 04:09:57 -04:00
{
2020-07-30 19:14:41 -04:00
public :
2021-03-31 04:09:57 -04:00
static constexpr float MaxPitchShiftOctaves = 6.0f ;
FWavePlayerOperator ( const FWavePlayerOpArgs & InArgs )
: OperatorSettings ( InArgs . Settings )
, PlayTrigger ( InArgs . PlayTrigger )
, StopTrigger ( InArgs . StopTrigger )
, WaveAsset ( InArgs . WaveAsset )
, StartTime ( InArgs . StartTime )
//, bStartTimeAsPercentage(InArgs.bStartTimeAsPercentage)
, PitchShift ( InArgs . PitchShift )
, bLoop ( InArgs . bLoop )
, LoopStartTime ( InArgs . LoopStartTime )
, LoopDurationSeconds ( InArgs . LoopDurationSeconds )
, TriggerOnPlay ( FTriggerWriteRef : : CreateNew ( InArgs . Settings ) )
, TriggerOnDone ( FTriggerWriteRef : : CreateNew ( InArgs . Settings ) )
, TriggerOnNearlyDone ( FTriggerWriteRef : : CreateNew ( InArgs . Settings ) )
, TriggerOnLooped ( FTriggerWriteRef : : CreateNew ( InArgs . Settings ) )
, TriggerOnCuePoint ( FTriggerWriteRef : : CreateNew ( InArgs . Settings ) )
, CuePointID ( FInt32WriteRef : : CreateNew ( 0 ) )
, CuePointLabel ( FStringWriteRef : : CreateNew ( TEXT ( " " ) ) )
, LoopPercent ( FFloatWriteRef : : CreateNew ( 0.0f ) )
, PlaybackLocation ( FFloatWriteRef : : CreateNew ( 0.0f ) )
, AudioBufferL ( FAudioBufferWriteRef : : CreateNew ( InArgs . Settings ) )
, AudioBufferR ( FAudioBufferWriteRef : : CreateNew ( InArgs . Settings ) )
2021-04-12 13:08:39 -04:00
, Decoder ( Audio : : FSimpleDecoderWrapper : : InitParams { static_cast < uint32 > ( InArgs . Settings . GetNumFramesPerBlock ( ) ) , InArgs . Settings . GetSampleRate ( ) , 6.f /*MaxPitchShiftMagnitudeAllowedInOctages */ } )
2021-03-31 04:09:57 -04:00
, OutputSampleRate ( InArgs . Settings . GetSampleRate ( ) )
, OutputBlockSizeInFrames ( InArgs . Settings . GetNumFramesPerBlock ( ) )
2020-07-30 19:14:41 -04:00
{
2021-01-25 06:10:37 -04:00
check ( OutputSampleRate ) ;
2021-01-24 16:12:59 -04:00
check ( AudioBufferL - > Num ( ) = = OutputBlockSizeInFrames & & AudioBufferR - > Num ( ) = = OutputBlockSizeInFrames ) ;
2020-07-30 19:14:41 -04:00
}
2020-07-28 11:45:27 -04:00
2020-12-08 19:34:18 -04:00
virtual FDataReferenceCollection GetInputs ( ) const override
2020-07-30 19:14:41 -04:00
{
2021-03-31 04:09:57 -04:00
using namespace WavePlayerVertexNames ;
2020-12-08 19:34:18 -04:00
FDataReferenceCollection InputDataReferences ;
2021-03-31 04:09:57 -04:00
InputDataReferences . AddDataReadReference ( InputTriggerPlayName , PlayTrigger ) ;
InputDataReferences . AddDataReadReference ( InputTriggerStopName , StopTrigger ) ;
InputDataReferences . AddDataReadReference ( InputWaveAssetName , WaveAsset ) ;
InputDataReferences . AddDataReadReference ( InputStartTimeName , StartTime ) ;
InputDataReferences . AddDataReadReference ( InputPitchShiftName , PitchShift ) ;
InputDataReferences . AddDataReadReference ( InputLoopName , bLoop ) ;
InputDataReferences . AddDataReadReference ( InputLoopStartName , LoopStartTime ) ;
InputDataReferences . AddDataReadReference ( InputLoopDurationName , LoopDurationSeconds ) ;
//InputDataReferences.AddDataReadReference(InputStartTimeAsPercentName, bStartTimeAsPercentage);
2020-07-30 19:14:41 -04:00
return InputDataReferences ;
}
2020-07-28 11:45:27 -04:00
2020-12-08 19:34:18 -04:00
virtual FDataReferenceCollection GetOutputs ( ) const override
2020-07-30 19:14:41 -04:00
{
2021-03-31 04:09:57 -04:00
using namespace WavePlayerVertexNames ;
2020-12-08 19:34:18 -04:00
FDataReferenceCollection OutputDataReferences ;
2021-03-31 04:09:57 -04:00
OutputDataReferences . AddDataReadReference ( OutputTriggerOnPlayName , TriggerOnPlay ) ;
OutputDataReferences . AddDataReadReference ( OutputTriggerOnDoneName , TriggerOnDone ) ;
OutputDataReferences . AddDataReadReference ( OutputTriggerOnNearlyDoneName , TriggerOnNearlyDone ) ;
OutputDataReferences . AddDataReadReference ( OutputTriggerOnLoopedName , TriggerOnLooped ) ;
OutputDataReferences . AddDataReadReference ( OutputTriggerOnCuePointName , TriggerOnCuePoint ) ;
OutputDataReferences . AddDataReadReference ( OutputCuePointIDName , CuePointID ) ;
OutputDataReferences . AddDataReadReference ( OutputCuePointLabelName , CuePointLabel ) ;
OutputDataReferences . AddDataReadReference ( OutputLoopPercentName , LoopPercent ) ;
OutputDataReferences . AddDataReadReference ( OutputPlaybackLocationName , PlaybackLocation ) ;
OutputDataReferences . AddDataReadReference ( OutputAudioLeftName , AudioBufferL ) ;
OutputDataReferences . AddDataReadReference ( OutputAudioRightName , AudioBufferR ) ;
2020-07-30 19:14:41 -04:00
return OutputDataReferences ;
}
2020-07-28 11:45:27 -04:00
2021-03-31 04:09:57 -04:00
float UpdateAndGetLoopStartTime ( )
{
float LoopSeekTime = 0.0f ;
// if (*bStartTimeAsPercentage)
// {
// float LoopStartTimeClamped = FMath::Clamp((float)LoopStartTime->GetSeconds(), 0.0f, 1.0f);
// LoopSeekTime = LoopStartTimeClamped * SoundAssetDurationSeconds;
// }
// else
{
LoopSeekTime = FMath : : Clamp ( ( float ) LoopStartTime - > GetSeconds ( ) , 0.0f , SoundAssetDurationSeconds ) ;
}
LoopStartFrame = SoundAssetSampleRate * LoopSeekTime ;
FramesToConsumePlayBeforeLooping = LoopStartFrame ;
if ( ! bLooped & & PlaybackStartFrame > LoopStartFrame )
{
FramesToConsumePlayBeforeLooping + = ( SoundAssetNumFrames - PlaybackStartFrame ) ;
}
return LoopSeekTime ;
}
2020-07-30 19:14:41 -04:00
void Execute ( )
{
2021-03-31 04:09:57 -04:00
TriggerOnPlay - > AdvanceBlock ( ) ;
TriggerOnDone - > AdvanceBlock ( ) ;
TriggerOnNearlyDone - > AdvanceBlock ( ) ;
TriggerOnCuePoint - > AdvanceBlock ( ) ;
TriggerOnLooped - > AdvanceBlock ( ) ;
2020-07-30 19:14:41 -04:00
2021-01-24 16:12:59 -04:00
// zero output buffers
FMemory : : Memzero ( AudioBufferL - > GetData ( ) , OutputBlockSizeInFrames * sizeof ( float ) ) ;
FMemory : : Memzero ( AudioBufferR - > GetData ( ) , OutputBlockSizeInFrames * sizeof ( float ) ) ;
2021-03-15 22:10:18 -04:00
// Parse triggers and render audio
int32 PlayTrigIndex = 0 ;
int32 NextPlayFrame = 0 ;
2021-03-31 04:09:57 -04:00
const int32 NumPlayTrigs = PlayTrigger - > NumTriggeredInBlock ( ) ;
2021-02-01 21:03:37 -04:00
2021-03-15 22:10:18 -04:00
int32 StopTrigIndex = 0 ;
int32 NextStopFrame = 0 ;
2021-03-31 04:09:57 -04:00
const int32 NumStopTrigs = StopTrigger - > NumTriggeredInBlock ( ) ;
2021-03-15 22:10:18 -04:00
int32 CurrAudioFrame = 0 ;
int32 NextAudioFrame = 0 ;
while ( NextAudioFrame < ( OutputBlockSizeInFrames - 1 ) )
{
const int32 NoTrigger = ( OutputBlockSizeInFrames < < 1 ) ;
2021-03-31 04:09:57 -04:00
// get the next Play and Stop indices
2021-03-15 22:10:18 -04:00
// (play)
if ( PlayTrigIndex < NumPlayTrigs )
{
2021-03-31 04:09:57 -04:00
NextPlayFrame = ( * PlayTrigger ) [ PlayTrigIndex ] ;
2021-01-24 16:12:59 -04:00
}
2021-03-15 22:10:18 -04:00
else
{
NextPlayFrame = NoTrigger ;
}
// (stop)
if ( StopTrigIndex < NumStopTrigs )
{
2021-03-31 04:09:57 -04:00
NextStopFrame = ( * StopTrigger ) [ StopTrigIndex ] ;
2021-03-15 22:10:18 -04:00
}
else
{
NextStopFrame = NoTrigger ;
}
// determine the next audio frame we are going to render up to
NextAudioFrame = FMath : : Min ( NextPlayFrame , NextStopFrame ) ;
// no more triggers, rendering to the end of the block
if ( NextAudioFrame = = NoTrigger )
{
NextAudioFrame = OutputBlockSizeInFrames ;
}
// render audio (while loop handles looping audio)
while ( CurrAudioFrame ! = NextAudioFrame )
{
if ( bIsPlaying )
{
CurrAudioFrame + = ExecuteInternal ( CurrAudioFrame , NextAudioFrame ) ;
}
else
{
CurrAudioFrame = NextAudioFrame ;
}
}
// execute the next trigger
if ( CurrAudioFrame = = NextPlayFrame )
{
2021-03-31 04:09:57 -04:00
if ( * StartTime > 0.0f )
{
ExecuteSeekRequest ( ) ;
}
2021-04-03 19:59:23 -04:00
TriggerOnPlay - > TriggerFrame ( CurrAudioFrame ) ;
2021-03-15 22:10:18 -04:00
StartPlaying ( ) ;
+ + PlayTrigIndex ;
}
if ( CurrAudioFrame = = NextStopFrame )
{
bIsPlaying = false ;
2021-03-31 04:09:57 -04:00
TriggerOnDone - > TriggerFrame ( CurrAudioFrame ) ;
2021-03-15 22:10:18 -04:00
+ + StopTrigIndex ;
}
}
}
void StartPlaying ( )
{
2021-04-12 13:08:39 -04:00
// Copy the wave asset off on init in case the user changes it while we're playing it.
// We'll only check for new wave assets when the current one finishes for sample accurate concantenation
CurrentWaveAsset = * WaveAsset ;
2021-03-31 04:09:57 -04:00
if ( IsWaveValid ( ) )
2021-03-15 22:10:18 -04:00
{
2021-08-09 15:13:40 -04:00
UE_LOG ( LogMetaSound , Verbose , TEXT ( " Starting Sound: '%s' " ) , * CurrentWaveAsset - > GetFullName ( ) ) ;
2021-03-31 04:09:57 -04:00
SoundAssetSampleRate = CurrentWaveAsset - > GetSampleRate ( ) ;
SoundAssetDurationSeconds = CurrentWaveAsset - > GetDuration ( ) ;
SoundAssetNumFrames = CurrentWaveAsset - > GetNumFrames ( ) ;
CurrentSoundWaveName = CurrentWaveAsset - > GetFName ( ) ;
PlaybackStartFrame = FMath : : Clamp ( ( float ) StartTime - > GetSeconds ( ) , 0.0f , SoundAssetDurationSeconds ) / SoundAssetSampleRate ;
float StartTimeSeconds = 0.0f ;
const FSoundWaveProxyPtr & WaveProxy = CurrentWaveAsset . GetSoundWaveProxy ( ) ;
// if (*bStartTimeAsPercentage)
// {
//
// float Percentage = FMath::Clamp((float)StartTime->GetSeconds(), 0.0f, 1.0f);
// StartTimeSeconds = Percentage * WaveProxy->GetDuration();
// }
// else
{
StartTimeSeconds = FMath : : Clamp ( ( float ) StartTime - > GetSeconds ( ) , 0.0f , WaveProxy - > GetDuration ( ) ) ;
}
PlaybackStartFrame = ( uint32 ) ( StartTimeSeconds * SoundAssetSampleRate ) ;
UpdateAndGetLoopStartTime ( ) ;
2021-04-12 13:08:39 -04:00
// Update our current loop frame
CurrentConsumedFrameCount = LoopStartFrame ;
// Reset our decoded frame counting for sub loop
NumTotalDecodedFramesInLoop = 0 ;
* LoopPercent = 0.0f ;
2021-03-31 04:09:57 -04:00
StartDecoder ( StartTimeSeconds , true /* bLogFailures */ ) ;
if ( ! bIsPlaying )
{
bIsPlaying = Decoder . CanGenerateAudio ( ) ;
}
2021-03-15 22:10:18 -04:00
}
2021-03-31 04:09:57 -04:00
2021-03-15 22:10:18 -04:00
}
void ExecuteSeekRequest ( )
{
2021-03-25 23:30:50 -04:00
// TODO: Don't do full decoder reset (as performed below) and instead seek decoder.
// ex: Decoder.SeekToTime(FMath::Max(0.f, *SeekTimeSeconds));
2021-03-15 22:10:18 -04:00
if ( ! bIsPlaying )
{
StartPlaying ( ) ;
}
2021-01-24 16:12:59 -04:00
}
2021-03-31 04:09:57 -04:00
bool IsWaveValidInternal ( const FWaveAsset & InWaveAsset , bool bReportToLog = false ) const
2021-03-25 23:30:50 -04:00
{
2021-03-31 04:09:57 -04:00
if ( ! InWaveAsset . IsSoundWaveValid ( ) )
2021-03-25 23:30:50 -04:00
{
if ( bReportToLog )
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to initialize SoundWave decoder. WavePlayerNode references invalid or missing Wave. " ) ) ;
2021-03-25 23:30:50 -04:00
}
return false ;
}
2021-03-31 04:09:57 -04:00
const FSoundWaveProxyPtr & WaveProxy = InWaveAsset . GetSoundWaveProxy ( ) ;
2021-03-25 23:30:50 -04:00
// WavePlayerNode currently only supports mono or stereo inputs
const int32 NumChannels = WaveProxy - > GetNumChannels ( ) ;
if ( NumChannels > 2 )
{
if ( bReportToLog )
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to initialize SoundWave decoder. WavePlayerNode only supports 2 channels max [%s: %d Channels]) " ) , * WaveProxy - > GetFullName ( ) , NumChannels ) ;
2021-03-25 23:30:50 -04:00
}
return false ;
}
return true ;
}
2021-03-31 04:09:57 -04:00
bool IsCurrentWaveValid ( bool bReportToLog = false ) const
2021-01-25 06:10:37 -04:00
{
2021-03-31 04:09:57 -04:00
return IsWaveValidInternal ( CurrentWaveAsset , bReportToLog ) ;
}
2021-01-25 06:10:37 -04:00
2021-03-31 04:09:57 -04:00
bool IsWaveValid ( bool bReportToLog = false ) const
{
return IsWaveValidInternal ( * WaveAsset , bReportToLog ) ;
}
float GetPlaybackRateFromPitchShift ( float InPitchShift )
{
return FMath : : Pow ( 2.0f , InPitchShift / 12.0f ) ;
}
bool StartDecoder ( float SeekTimeSeconds , bool bLogFailures = false )
{
if ( IsCurrentWaveValid ( bLogFailures ) )
2021-01-25 06:10:37 -04:00
{
2021-03-31 04:09:57 -04:00
const FSoundWaveProxyPtr & WaveProxy = CurrentWaveAsset . GetSoundWaveProxy ( ) ;
2021-04-12 13:08:39 -04:00
2021-03-31 04:09:57 -04:00
CuePoints = WaveProxy - > GetCuePoints ( ) ;
float PitchShiftClamped = FMath : : Clamp ( * PitchShift , - 12.0f * MaxPitchShiftOctaves , 12.0f * MaxPitchShiftOctaves ) ;
2021-04-12 13:08:39 -04:00
return Decoder . SetWave ( WaveProxy , SeekTimeSeconds , PitchShiftClamped ) ;
2021-04-12 03:49:32 -04:00
}
return false ;
}
bool SeekDecoder ( float SeekTimeSeconds , bool bLogFailures = false )
{
2021-04-12 13:08:39 -04:00
return Decoder . SeekToTime ( SeekTimeSeconds ) ;
2021-01-25 06:10:37 -04:00
}
2021-01-24 16:12:59 -04:00
2021-03-15 22:10:18 -04:00
int32 ExecuteInternal ( int32 StartFrame , int32 EndFrame )
2021-01-24 16:12:59 -04:00
{
2021-01-25 06:10:37 -04:00
// note: output is hard-coded to stereo (dual-mono)
2021-01-24 16:12:59 -04:00
float * FinalOutputLeft = AudioBufferL - > GetData ( ) + StartFrame ;
float * FinalOutputRight = AudioBufferR - > GetData ( ) + StartFrame ;
2021-03-31 04:09:57 -04:00
int32 NumOutputFramesToGenerate = ( EndFrame - StartFrame ) ;
int32 NumFramesGenerated = 0 ;
2021-01-24 16:12:59 -04:00
2021-04-15 11:24:11 -04:00
const bool bCanDecoderGenerateAudio = Decoder . CanGenerateAudio ( ) ;
if ( bCanDecoderGenerateAudio )
2021-01-24 16:12:59 -04:00
{
2021-02-10 21:43:31 -04:00
ensure ( Decoder . CanGenerateAudio ( ) ) ;
2021-01-24 16:12:59 -04:00
2021-04-15 11:24:11 -04:00
const int32 NumInputChannels = Decoder . GetNumChannels ( ) ;
2021-02-10 21:43:31 -04:00
const bool bNeedsUpmix = ( NumInputChannels = = 1 ) ;
const bool bNeedsDeinterleave = ! bNeedsUpmix ;
2021-03-31 04:09:57 -04:00
const int32 NumSamplesToGenerate = NumOutputFramesToGenerate * NumInputChannels ;
float PitchShiftClamped = FMath : : Clamp ( * PitchShift , - 12.0f * MaxPitchShiftOctaves , 12.0f * MaxPitchShiftOctaves ) ;
2021-02-10 21:43:31 -04:00
PostSrcBuffer . Reset ( NumSamplesToGenerate ) ;
PostSrcBuffer . AddZeroed ( NumSamplesToGenerate ) ;
float * PostSrcBufferPtr = PostSrcBuffer . GetData ( ) ;
2021-03-31 04:09:57 -04:00
// Update looping state which may have changed
bool bIsLooping = * bLoop ;
2021-02-10 21:43:31 -04:00
2021-03-31 04:09:57 -04:00
// If we're looping we got some extra logic we need to do before we generate audio
if ( * bLoop )
{
float LoopDuration = SoundAssetDurationSeconds ;
if ( LoopDurationSeconds - > GetSeconds ( ) > 0.0f )
{
LoopDuration = FMath : : Clamp ( ( float ) LoopDurationSeconds - > GetSeconds ( ) , 0.0f , SoundAssetDurationSeconds ) ;
}
// Give the loop frame a minimum width
LoopNumFrames = ( uint32 ) FMath : : Max ( LoopDuration * SoundAssetSampleRate , 10.0f ) ;
// We need to look for the case where we are about to loop and we don't want to "overshoot" the loop
// So we need to check the number output frames to generate against how much is left in our loop
if ( NumTotalDecodedFramesInLoop < LoopNumFrames )
{
// The number of frames left in the loop we need to consume from the source file
int32 NumFramesLeftToConsumeInLoop = ( int32 ) LoopNumFrames - NumTotalDecodedFramesInLoop ;
// Translate that to a generated frames count taking into account the pitch scale and the playback rate.
// Note this is going to be slightly inaccurate due to pitch interpolation... but it should be super close
// and w/ loop cross fading implemented, won't matter too much.
float PlaybackRate = GetPlaybackRateFromPitchShift ( PitchShiftClamped ) ;
float SampleRateRatioWithPitchScale = PlaybackRate * SoundAssetSampleRate / OutputSampleRate ;
int32 NumFramesLeftToGenerateForLoop = SampleRateRatioWithPitchScale * NumFramesLeftToConsumeInLoop ;
// Generate the min of the requested frames to generate and the amount we need to finish the loop
NumOutputFramesToGenerate = FMath : : Min ( NumOutputFramesToGenerate , NumFramesLeftToGenerateForLoop ) ;
}
else
{
NumOutputFramesToGenerate = 1 ;
}
}
else
{
LoopNumFrames = 0 ;
NumTotalDecodedFramesInLoop = 0 ;
}
int32 NumFramesConsumed = 0 ;
if ( NumOutputFramesToGenerate > 0 )
{
NumFramesGenerated = Decoder . GenerateAudio ( PostSrcBufferPtr , NumOutputFramesToGenerate , NumFramesConsumed , 100.0f * PitchShiftClamped , bIsLooping ) / NumInputChannels ;
// Update loop and playback state logic based on the number of frames consumed
FramesConsumedSinceStart + = NumFramesConsumed ;
int32 PrevConsumedFrameCount = CurrentConsumedFrameCount ;
CurrentConsumedFrameCount = ( CurrentConsumedFrameCount + NumFramesConsumed ) % SoundAssetNumFrames ;
2021-04-03 19:59:23 -04:00
* PlaybackLocation = SoundAssetDurationSeconds * ( ( float ) CurrentConsumedFrameCount / SoundAssetNumFrames ) ;
2021-03-31 04:09:57 -04:00
// Check for any cue trigger events
FSoundWaveCuePoint OutCuePoint ;
int32 OutFrameOffset = INDEX_NONE ;
if ( GetCuePointForBlock ( PrevConsumedFrameCount , CurrentConsumedFrameCount , OutCuePoint , OutFrameOffset ) )
{
// Write the outputs
* CuePointID = OutCuePoint . CuePointID ;
* CuePointLabel = OutCuePoint . Label ;
// Do the trigger
TriggerOnCuePoint - > TriggerFrame ( StartFrame + OutFrameOffset ) ;
}
// Check for nearly done trigger logic. If we're not looping and the next render block will likely finish the file.
if ( ! bIsLooping & & ( CurrentConsumedFrameCount + OutputBlockSizeInFrames ) > = SoundAssetNumFrames )
{
TriggerOnNearlyDone - > TriggerFrame ( StartFrame + NumFramesGenerated ) ;
}
}
// If we're looping and we've made it past the loop start point and we've not yet looped, check the looping condition
// Note: we need to handle the case where the "start time" of the player is past the loop start point. In that case
// we allow the audio file to wrap around and loop from the beginning before checking the loop logic
if ( bIsLooping & & ( bLooped | | FramesConsumedSinceStart > = FramesToConsumePlayBeforeLooping ) )
{
NumTotalDecodedFramesInLoop + = NumFramesConsumed ;
if ( NumTotalDecodedFramesInLoop > = LoopNumFrames | | ! NumOutputFramesToGenerate )
{
// We've looped now so we will continue to loop between the loop start and end points
bLooped = true ;
// Trigger that we looped
TriggerOnLooped - > TriggerFrame ( StartFrame + NumFramesGenerated ) ;
// Update the loop start frame based on any recent inputs
float LoopSeekTime = UpdateAndGetLoopStartTime ( ) ;
// Update our current loop frame
CurrentConsumedFrameCount = LoopStartFrame ;
// Reset our decoded frame counting for sub loop
NumTotalDecodedFramesInLoop = 0 ;
* LoopPercent = 0.0f ;
2021-07-14 19:13:44 -04:00
// Now seek to the loop start frame if we are looping a custom amount. Otherwise, we let the decoder do the looping.
if ( LoopDurationSeconds - > GetSeconds ( ) > 0.0f )
{
SeekDecoder ( LoopSeekTime ) ;
}
2021-03-31 04:09:57 -04:00
}
else
{
* LoopPercent = ( float ) NumTotalDecodedFramesInLoop / LoopNumFrames ;
}
}
else
{
* LoopPercent = 0.0f ;
}
2021-03-10 17:57:58 -04:00
// handle decoder having completed during it's decode
2021-04-15 11:24:11 -04:00
const bool bCannotGenerateAudio = ! Decoder . CanGenerateAudio ( ) ;
const bool bDidNotGenerateEnough = NumFramesGenerated < NumOutputFramesToGenerate ;
2021-03-31 04:09:57 -04:00
const bool bReachedEOF = ! Decoder . CanGenerateAudio ( ) | | ( NumFramesGenerated < NumOutputFramesToGenerate ) ;
2021-03-15 22:10:18 -04:00
if ( bReachedEOF & & ! bIsLooping )
2021-01-24 16:12:59 -04:00
{
2021-03-31 04:09:57 -04:00
// Check the wave file input to see if it has changed... if it has, we want to restart everything up!
bool bConcatenatedPlayback = false ;
if ( WaveAsset - > IsSoundWaveValid ( ) )
{
const FName SoundWaveName = ( * WaveAsset ) - > GetFName ( ) ;
if ( SoundWaveName ! = CurrentSoundWaveName )
{
StartPlaying ( ) ;
bConcatenatedPlayback = true ;
}
}
if ( ! bConcatenatedPlayback )
{
bIsPlaying = false ;
TriggerOnDone - > TriggerFrame ( StartFrame + NumFramesGenerated ) ;
}
2021-03-15 22:10:18 -04:00
}
2021-02-10 21:43:31 -04:00
if ( bNeedsUpmix )
{
2021-03-31 04:09:57 -04:00
// Dual mono output
FMemory : : Memcpy ( FinalOutputLeft , PostSrcBufferPtr , sizeof ( float ) * NumFramesGenerated ) ;
FMemory : : Memcpy ( FinalOutputRight , PostSrcBufferPtr , sizeof ( float ) * NumFramesGenerated ) ;
2021-02-10 21:43:31 -04:00
}
else if ( bNeedsDeinterleave )
{
2021-03-31 04:09:57 -04:00
for ( int32 i = 0 ; i < NumFramesGenerated ; + + i )
2021-02-10 21:43:31 -04:00
{
2021-03-25 23:30:50 -04:00
// deinterleave each stereo frame into output buffers
2021-02-10 21:43:31 -04:00
FinalOutputLeft [ i ] = PostSrcBufferPtr [ ( i < < 1 ) ] ;
FinalOutputRight [ i ] = PostSrcBufferPtr [ ( i < < 1 ) + 1 ] ;
}
}
}
else
{
2021-03-31 04:09:57 -04:00
FMemory : : Memzero ( FinalOutputLeft , sizeof ( float ) * NumOutputFramesToGenerate ) ;
FMemory : : Memzero ( FinalOutputRight , sizeof ( float ) * NumOutputFramesToGenerate ) ;
2021-04-12 13:08:39 -04:00
NumFramesGenerated = NumOutputFramesToGenerate ;
2020-07-30 19:14:41 -04:00
}
2021-03-15 22:10:18 -04:00
2021-03-31 04:09:57 -04:00
return NumFramesGenerated ;
2020-07-30 19:14:41 -04:00
}
2020-07-28 11:45:27 -04:00
2020-07-30 19:14:41 -04:00
private :
2021-03-31 04:09:57 -04:00
// Called to get the cue point in the given block. Will return the first cue point if multiple cues are in the same block.
// Note: we can't currently support sub-block cue triggers due to cue ID and cue label being block-rate params.
bool GetCuePointForBlock ( int32 InStartFrame , int32 InEndFrame , FSoundWaveCuePoint & OutCuePoint , int32 & OutFrameOffset )
{
for ( FSoundWaveCuePoint & CuePoint : CuePoints )
{
if ( CuePoint . FramePosition > = InStartFrame & & CuePoint . FramePosition < InEndFrame )
{
OutCuePoint = CuePoint ;
OutFrameOffset = CuePoint . FramePosition - InStartFrame ;
return true ;
}
}
// No cue points in this block
return false ;
}
2020-07-30 19:14:41 -04:00
const FOperatorSettings OperatorSettings ;
2020-07-28 11:45:27 -04:00
2021-01-24 16:12:59 -04:00
// i/o
2021-03-31 04:09:57 -04:00
FTriggerReadRef PlayTrigger ;
FTriggerReadRef StopTrigger ;
FWaveAssetReadRef WaveAsset ;
FTimeReadRef StartTime ;
//FBoolReadRef bStartTimeAsPercentage;
FFloatReadRef PitchShift ;
FBoolReadRef bLoop ;
FTimeReadRef LoopStartTime ;
FTimeReadRef LoopDurationSeconds ;
2021-01-24 16:12:59 -04:00
2021-03-31 04:09:57 -04:00
FTriggerWriteRef TriggerOnPlay ;
FTriggerWriteRef TriggerOnDone ;
FTriggerWriteRef TriggerOnNearlyDone ;
FTriggerWriteRef TriggerOnLooped ;
FTriggerWriteRef TriggerOnCuePoint ;
FInt32WriteRef CuePointID ;
FStringWriteRef CuePointLabel ;
FFloatWriteRef LoopPercent ;
FFloatWriteRef PlaybackLocation ;
2021-01-24 16:12:59 -04:00
FAudioBufferWriteRef AudioBufferL ;
FAudioBufferWriteRef AudioBufferR ;
2021-01-25 06:10:37 -04:00
// source decode
2021-03-25 23:30:50 -04:00
Audio : : FAlignedFloatBuffer PostSrcBuffer ;
2021-01-25 06:10:37 -04:00
Audio : : FSimpleDecoderWrapper Decoder ;
2021-01-24 16:12:59 -04:00
2021-03-31 04:09:57 -04:00
FName CurrentSoundWaveName ;
2021-01-25 06:10:37 -04:00
2021-03-31 04:09:57 -04:00
float SoundAssetSampleRate = 0.0f ;
float SoundAssetDurationSeconds = 0.0f ;
uint32 SoundAssetNumFrames = 0 ;
const float OutputSampleRate = 0.f ;
const int32 OutputBlockSizeInFrames = 0 ;
2021-01-24 16:12:59 -04:00
2021-03-31 04:09:57 -04:00
TArray < FSoundWaveCuePoint > CuePoints ;
TArray < FSoundWaveCuePoint > CuePointsInCurrentBock ;
// Data to track decoder
uint32 PlaybackStartFrame = 0 ;
uint32 CurrentConsumedFrameCount = 0 ;
uint32 FramesConsumedSinceStart = 0 ;
uint32 NumTotalDecodedFramesInLoop = 0 ;
uint32 LoopStartFrame = 0 ;
uint32 FramesToConsumePlayBeforeLooping = 0 ;
uint32 LoopNumFrames = 0 ;
bool bLooped = false ;
bool bIsPlaying = false ;
FWaveAsset CurrentWaveAsset ;
2021-01-24 16:12:59 -04:00
} ;
2020-07-28 11:45:27 -04:00
TUniquePtr < IOperator > FWavePlayerNode : : FOperatorFactory : : CreateOperator (
2020-08-24 10:57:03 -04:00
const FCreateOperatorParams & InParams ,
FBuildErrorArray & OutErrors )
2020-07-28 11:45:27 -04:00
{
using namespace Audio ;
2021-03-31 04:09:57 -04:00
using namespace WavePlayerVertexNames ;
2020-07-28 11:45:27 -04:00
2020-08-24 10:57:03 -04:00
const FWavePlayerNode & WaveNode = static_cast < const FWavePlayerNode & > ( InParams . Node ) ;
2020-07-28 11:45:27 -04:00
2021-03-31 04:09:57 -04:00
const FDataReferenceCollection & Inputs = InParams . InputDataReferences ;
2020-08-24 10:57:03 -04:00
2021-03-31 04:09:57 -04:00
FWavePlayerOpArgs Args =
{
InParams . OperatorSettings ,
Inputs . GetDataReadReferenceOrConstruct < FTrigger > ( InputTriggerPlayName , InParams . OperatorSettings ) ,
Inputs . GetDataReadReferenceOrConstruct < FTrigger > ( InputTriggerStopName , InParams . OperatorSettings ) ,
Inputs . GetDataReadReferenceOrConstruct < FWaveAsset > ( InputWaveAssetName ) ,
Inputs . GetDataReadReferenceOrConstruct < FTime > ( InputStartTimeName ) ,
Inputs . GetDataReadReferenceOrConstruct < float > ( InputPitchShiftName ) ,
Inputs . GetDataReadReferenceOrConstruct < bool > ( InputLoopName ) ,
Inputs . GetDataReadReferenceOrConstruct < FTime > ( InputLoopStartName ) ,
Inputs . GetDataReadReferenceOrConstruct < FTime > ( InputLoopDurationName )
//Inputs.GetDataReadReferenceOrConstruct<bool>(InputStartTimeAsPercentName)
} ;
2021-03-15 22:10:18 -04:00
2021-03-31 04:09:57 -04:00
return MakeUnique < FWavePlayerOperator > ( Args ) ;
2020-07-28 11:45:27 -04:00
}
2020-11-24 15:24:29 -04:00
FVertexInterface FWavePlayerNode : : DeclareVertexInterface ( )
{
2021-03-31 04:09:57 -04:00
using namespace WavePlayerVertexNames ;
2020-11-24 15:24:29 -04:00
return FVertexInterface (
FInputVertexInterface (
2021-03-31 04:09:57 -04:00
TInputDataVertexModel < FTrigger > ( InputTriggerPlayName , InputTriggerPlayTT ) ,
TInputDataVertexModel < FTrigger > ( InputTriggerStopName , InputTriggerStopTT ) ,
TInputDataVertexModel < FWaveAsset > ( InputWaveAssetName , InputWaveAssetTT ) ,
TInputDataVertexModel < FTime > ( InputStartTimeName , InputStartTimeTT , 0.0f ) ,
TInputDataVertexModel < float > ( InputPitchShiftName , InputPitchShiftTT , 0.0f ) ,
TInputDataVertexModel < bool > ( InputLoopName , InputLoopTT , false ) ,
TInputDataVertexModel < FTime > ( InputLoopStartName , InputLoopStartTT , 0.0f ) ,
TInputDataVertexModel < FTime > ( InputLoopDurationName , InputLoopDurationTT , - 1.0f )
//TInputDataVertexModel<bool>(InputStartTimeAsPercentName, InputStartTimeAsPercentTT, false)
) ,
2020-11-24 15:24:29 -04:00
FOutputVertexInterface (
2021-03-31 04:09:57 -04:00
TOutputDataVertexModel < FTrigger > ( OutputTriggerOnPlayName , OutputTriggerOnPlayTT ) ,
TOutputDataVertexModel < FTrigger > ( OutputTriggerOnDoneName , OutputTriggerOnDoneTT ) ,
TOutputDataVertexModel < FTrigger > ( OutputTriggerOnNearlyDoneName , OutputTriggerOnNearlyDoneTT ) ,
TOutputDataVertexModel < FTrigger > ( OutputTriggerOnLoopedName , OutputTriggerOnLoopedTT ) ,
TOutputDataVertexModel < FTrigger > ( OutputTriggerOnCuePointName , OutputTriggerOnCuePointTT ) ,
TOutputDataVertexModel < int32 > ( OutputCuePointIDName , OutputCuePointIDTT ) ,
TOutputDataVertexModel < FString > ( OutputCuePointLabelName , OutputCuePointLabelTT ) ,
TOutputDataVertexModel < float > ( OutputLoopPercentName , OutputAudioRightNameTT ) ,
TOutputDataVertexModel < float > ( OutputPlaybackLocationName , OutputPlaybackLocationTT ) ,
TOutputDataVertexModel < FAudioBuffer > ( OutputAudioLeftName , OutputAudioLeftNameTT ) ,
TOutputDataVertexModel < FAudioBuffer > ( OutputAudioRightName , OutputAudioRightNameTT )
2020-11-24 15:24:29 -04:00
)
) ;
}
2021-01-28 19:02:51 -04:00
const FNodeClassMetadata & FWavePlayerNode : : GetNodeInfo ( )
2020-11-24 15:24:29 -04:00
{
2021-01-28 19:02:51 -04:00
auto InitNodeInfo = [ ] ( ) - > FNodeClassMetadata
2020-11-24 15:24:29 -04:00
{
2021-01-28 19:02:51 -04:00
FNodeClassMetadata Info ;
2021-02-01 21:03:37 -04:00
Info . ClassName = { Metasound : : EngineNodes : : Namespace , TEXT ( " Wave Player " ) , Metasound : : EngineNodes : : StereoVariant } ;
2020-11-24 15:24:29 -04:00
Info . MajorVersion = 1 ;
Info . MinorVersion = 0 ;
2021-03-22 11:02:28 -04:00
Info . DisplayName = LOCTEXT ( " Metasound_WavePlayerNodeDisplayName " , " Wave Player " ) ;
2021-03-31 04:09:57 -04:00
Info . Description = LOCTEXT ( " Metasound_WavePlayerNodeDescription " , " Plays a wave asset. " ) ;
2020-11-24 15:24:29 -04:00
Info . Author = PluginAuthor ;
Info . PromptIfMissing = PluginNodeMissingPrompt ;
Info . DefaultInterface = DeclareVertexInterface ( ) ;
return Info ;
} ;
2021-01-28 19:02:51 -04:00
static const FNodeClassMetadata Info = InitNodeInfo ( ) ;
2020-11-24 15:24:29 -04:00
return Info ;
}
2021-09-13 14:14:37 -04:00
FWavePlayerNode : : FWavePlayerNode ( const FVertexName & InName , const FGuid & InInstanceID )
2021-02-01 21:03:37 -04:00
: FNode ( InName , InInstanceID , GetNodeInfo ( ) )
, Factory ( MakeOperatorFactoryRef < FWavePlayerNode : : FOperatorFactory > ( ) )
, Interface ( DeclareVertexInterface ( ) )
2020-07-28 11:45:27 -04:00
{
}
FWavePlayerNode : : FWavePlayerNode ( const FNodeInitData & InInitData )
2021-02-01 21:03:37 -04:00
: FWavePlayerNode ( InInitData . InstanceName , InInitData . InstanceID )
2020-07-28 11:45:27 -04:00
{
}
2020-11-24 15:24:29 -04:00
FOperatorFactorySharedRef FWavePlayerNode : : GetDefaultOperatorFactory ( ) const
2020-07-28 11:45:27 -04:00
{
return Factory ;
}
2020-08-24 10:57:03 -04:00
2020-11-24 15:24:29 -04:00
const FVertexInterface & FWavePlayerNode : : GetVertexInterface ( ) const
2020-08-24 10:57:03 -04:00
{
return Interface ;
}
bool FWavePlayerNode : : SetVertexInterface ( const FVertexInterface & InInterface )
{
return InInterface = = Interface ;
}
bool FWavePlayerNode : : IsVertexInterfaceSupported ( const FVertexInterface & InInterface ) const
{
return InInterface = = Interface ;
}
2020-11-18 17:34:04 -04:00
METASOUND_REGISTER_NODE ( FWavePlayerNode )
2021-01-24 16:12:59 -04:00
2020-11-18 17:34:04 -04:00
} // namespace Metasound
# undef LOCTEXT_NAMESPACE // MetasoundWaveNode