2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-08-05 20:07:14 -04:00
# include "AudioAnalyzerNRT.h"
# include "AudioAnalyzerNRTFacade.h"
# include "AudioAnalyzerModule.h"
2019-08-26 18:35:22 -04:00
# include "SampleBuffer.h"
2019-08-05 20:07:14 -04:00
# include "Async/Async.h"
2019-10-31 12:19:40 -04:00
2019-08-05 20:07:14 -04:00
# if WITH_EDITOR
namespace
{
2021-01-09 18:31:09 -04:00
class FAudioAnalyzeNRTTask : public FNonAbandonableTask
2019-08-05 20:07:14 -04:00
{
2021-01-09 18:31:09 -04:00
friend class FAutoDeleteAsyncTask < FAudioAnalyzeNRTTask > ;
2019-08-05 20:07:14 -04:00
public :
2021-01-09 18:31:09 -04:00
FAudioAnalyzeNRTTask (
2019-08-05 20:07:14 -04:00
TWeakObjectPtr < UAudioAnalyzerNRT > InAnalyzerUObject ,
2019-10-31 12:19:40 -04:00
const UAudioAnalyzerNRT : : FResultId InResultId ,
2021-01-09 18:31:09 -04:00
TUniquePtr < Audio : : FAnalyzerNRTFacade > & & InAnalyzerFacade ,
2019-08-05 20:07:14 -04:00
TArray < uint8 > & & InRawWaveData ,
int32 InNumChannels ,
float InSampleRate )
: AnalyzerUObject ( InAnalyzerUObject )
2019-10-31 12:19:40 -04:00
, ResultId ( InResultId )
2019-08-05 20:07:14 -04:00
, AnalyzerFacade ( MoveTemp ( InAnalyzerFacade ) )
, RawWaveData ( MoveTemp ( InRawWaveData ) )
, NumChannels ( InNumChannels )
, SampleRate ( InSampleRate )
{ }
void DoWork ( )
{
TUniquePtr < Audio : : IAnalyzerNRTResult > Result = AnalyzerFacade - > AnalyzePCM16Audio ( RawWaveData , NumChannels , SampleRate ) ;
2019-10-31 12:19:40 -04:00
UAudioAnalyzerNRT : : FResultSharedPtr ResultPtr ( Result . Release ( ) ) ;
2019-08-05 20:07:14 -04:00
// Set value on game thread.
2019-10-31 12:19:40 -04:00
AsyncTask ( ENamedThreads : : GameThread , [ Analyzer = AnalyzerUObject , ThisResultId = ResultId , ResultPtr ] ( ) {
2019-08-05 20:07:14 -04:00
if ( Analyzer . IsValid ( ) )
{
2019-10-31 12:19:40 -04:00
Analyzer - > SetResultIfLatest ( ResultPtr , ThisResultId ) ;
2019-08-05 20:07:14 -04:00
}
} ) ;
}
2021-01-09 18:31:09 -04:00
FORCEINLINE TStatId GetStatId ( ) const { RETURN_QUICK_DECLARE_CYCLE_STAT ( AudioAnalyzeNRTTask , STATGROUP_ThreadPoolAsyncTasks ) ; }
2019-08-05 20:07:14 -04:00
private :
TWeakObjectPtr < UAudioAnalyzerNRT > AnalyzerUObject ;
2019-10-31 12:19:40 -04:00
const UAudioAnalyzerNRT : : FResultId ResultId ;
2021-01-09 18:31:09 -04:00
TUniquePtr < Audio : : FAnalyzerNRTFacade > AnalyzerFacade ;
2019-08-05 20:07:14 -04:00
TArray < uint8 > RawWaveData ;
int32 NumChannels ;
float SampleRate ;
} ;
}
/*****************************************************/
/*********** UAudioAnalyzerNRTSettings ***************/
/*****************************************************/
void UAudioAnalyzerNRTSettings : : PostEditChangeProperty ( struct FPropertyChangedEvent & PropertyChangedEvent )
{
Super : : PostEditChangeProperty ( PropertyChangedEvent ) ;
if ( ShouldEventTriggerAnalysis ( PropertyChangedEvent ) )
{
2019-10-31 12:19:40 -04:00
AnalyzeAudioDelegate . Broadcast ( ) ;
2019-08-05 20:07:14 -04:00
}
}
bool UAudioAnalyzerNRTSettings : : ShouldEventTriggerAnalysis ( struct FPropertyChangedEvent & PropertyChangeEvent )
{
2019-10-31 12:19:40 -04:00
// By default, all non-interactive changes to settings will trigger analysis.
return PropertyChangeEvent . ChangeType ! = EPropertyChangeType : : Interactive ;
2019-08-05 20:07:14 -04:00
}
/*****************************************************/
/*********** UAudioAnalyzerNRT ***************/
/*****************************************************/
2019-12-13 11:07:03 -05:00
void UAudioAnalyzerNRT : : PreEditChange ( FProperty * PropertyAboutToChange )
2019-08-05 20:07:14 -04:00
{
// If the settings object is replaced, need to unbind any existing settings objects
// from calling the analyze audio delegate.
Super : : PreEditChange ( PropertyAboutToChange ) ;
UAudioAnalyzerNRTSettings * Settings = GetSettingsFromProperty ( PropertyAboutToChange ) ;
if ( Settings )
{
2019-10-31 12:19:40 -04:00
RemoveSettingsDelegate ( Settings ) ;
2019-08-05 20:07:14 -04:00
}
}
void UAudioAnalyzerNRT : : PostEditChangeProperty ( struct FPropertyChangedEvent & PropertyChangedEvent )
{
Super : : PostEditChangeProperty ( PropertyChangedEvent ) ;
// Check if the edited property was a UAudioAnalyzerNRTSettings object
UAudioAnalyzerNRTSettings * Settings = GetSettingsFromProperty ( PropertyChangedEvent . Property ) ;
if ( Settings )
{
// If it was a UAudioAnalyzerNRTSettings object, bind the FAnalyzeAudioDelegate
2019-10-31 12:19:40 -04:00
SetSettingsDelegate ( Settings ) ;
2019-08-05 20:07:14 -04:00
}
if ( ShouldEventTriggerAnalysis ( PropertyChangedEvent ) )
{
AnalyzeAudio ( ) ;
}
}
bool UAudioAnalyzerNRT : : ShouldEventTriggerAnalysis ( struct FPropertyChangedEvent & PropertyChangeEvent )
{
// by default, all changes will trigger analysis
return true ;
}
void UAudioAnalyzerNRT : : AnalyzeAudio ( )
{
TSharedPtr < Audio : : IAnalyzerNRTResult , ESPMode : : ThreadSafe > NewResult ;
2019-10-31 12:19:40 -04:00
// Create a new result id for this result.
FResultId ThisResultId = + + CurrentResultId ;
2019-08-05 20:07:14 -04:00
if ( nullptr ! = Sound )
{
// Read audio while Sound object is assured safe.
if ( Sound - > ChannelSizes . Num ( ) > 0 )
{
UE_LOG ( LogAudioAnalyzer , Warning , TEXT ( " Soundwave '%s' has multi-channel audio (channels greater than 2). Audio analysis is not currently supported for this yet. " ) , * Sound - > GetFullName ( ) ) ;
return ;
}
// Retrieve the raw imported data
TArray < uint8 > RawWaveData ;
uint32 SampleRate = 0 ;
uint16 NumChannels = 0 ;
if ( ! Sound - > GetImportedSoundWaveData ( RawWaveData , SampleRate , NumChannels ) )
{
UE_LOG ( LogAudioAnalyzer , Error , TEXT ( " Could not analyze audio due to failed import of sound wave data from Soundwave '%s'. " ) , * Sound - > GetFullName ( ) ) ;
return ;
}
if ( SampleRate = = 0 | | NumChannels = = 0 )
{
UE_LOG ( LogAudioAnalyzer , Error , TEXT ( " Failed to parse the raw imported data for '%s' for analysis. " ) , * Sound - > GetFullName ( ) ) ;
return ;
}
// Create analyzer helper object
2021-01-09 18:31:09 -04:00
TUniquePtr < Audio : : FAnalyzerNRTFacade > BatchAnalyzer = MakeUnique < Audio : : FAnalyzerNRTFacade > ( GetSettings ( SampleRate , NumChannels ) , GetAnalyzerNRTFactoryName ( ) ) ;
2019-08-05 20:07:14 -04:00
// Use weak reference in case this object is deleted before analysis is done
TWeakObjectPtr < UAudioAnalyzerNRT > AnalyzerPtr ( this ) ;
2019-08-08 01:03:29 -04:00
// Create and start async task. Parentheses avoids memory leak warnings from static analysis.
2021-01-09 18:31:09 -04:00
( new FAutoDeleteAsyncTask < FAudioAnalyzeNRTTask > ( AnalyzerPtr , ThisResultId , MoveTemp ( BatchAnalyzer ) , MoveTemp ( RawWaveData ) , NumChannels , SampleRate ) ) - > StartBackgroundTask ( ) ;
2019-08-05 20:07:14 -04:00
}
else
{
// Copy empty result to this object
SetResult ( nullptr ) ;
}
}
// Returns UAudioAnalyzerNRTSettings* if property points to a valid UAudioAnalyzerNRTSettings, otherwise returns nullptr.
2019-12-13 11:07:03 -05:00
UAudioAnalyzerNRTSettings * UAudioAnalyzerNRT : : GetSettingsFromProperty ( FProperty * Property )
2019-08-05 20:07:14 -04:00
{
if ( nullptr = = Property )
{
return nullptr ;
}
2019-12-13 11:07:03 -05:00
if ( Property - > IsA ( FObjectPropertyBase : : StaticClass ( ) ) )
2019-08-05 20:07:14 -04:00
{
2019-12-13 11:07:03 -05:00
FObjectPropertyBase * ObjectPropertyBase = CastFieldChecked < FObjectPropertyBase > ( Property ) ;
2019-08-05 20:07:14 -04:00
if ( nullptr = = ObjectPropertyBase )
{
return nullptr ;
}
if ( ObjectPropertyBase - > PropertyClass - > IsChildOf ( UAudioAnalyzerNRTSettings : : StaticClass ( ) ) )
{
UObject * PropertyObject = ObjectPropertyBase - > GetObjectPropertyValue_InContainer ( this ) ;
return Cast < UAudioAnalyzerNRTSettings > ( PropertyObject ) ;
}
}
return nullptr ;
}
2019-10-31 12:19:40 -04:00
void UAudioAnalyzerNRT : : SetResult ( FResultSharedPtr NewResult )
2019-08-05 20:07:14 -04:00
{
FScopeLock ResultLock ( & ResultCriticalSection ) ;
Result = NewResult ;
2019-10-31 12:19:40 -04:00
if ( Result . IsValid ( ) )
{
DurationInSeconds = Result - > GetDurationInSeconds ( ) ;
}
else
{
DurationInSeconds = 0.f ;
}
Modify ( ) ;
}
void UAudioAnalyzerNRT : : SetResultIfLatest ( FResultSharedPtr NewResult , FResultId InResultId )
{
FScopeLock ResultLock ( & ResultCriticalSection ) ;
const FResultId ResultId = CurrentResultId . Load ( ) ;
if ( ResultId = = InResultId )
{
Result = NewResult ;
if ( Result . IsValid ( ) )
{
DurationInSeconds = Result - > GetDurationInSeconds ( ) ;
}
else
{
DurationInSeconds = 0.f ;
}
Modify ( ) ;
}
2019-08-05 20:07:14 -04:00
}
# endif
void UAudioAnalyzerNRT : : Serialize ( FArchive & Ar )
{
// default uobject serialize
Super : : Serialize ( Ar ) ;
// When loading object, Result pointer is invalid. Need to create a valid
// result object for loading.
if ( ! Result . IsValid ( ) )
{
if ( ! GetClass ( ) - > HasAnyClassFlags ( CLASS_Abstract ) )
{
Audio : : IAnalyzerNRTFactory * Factory = Audio : : GetAnalyzerNRTFactory ( GetAnalyzerNRTFactoryName ( ) ) ;
if ( nullptr ! = Factory )
{
// Create result and worker from factory
{
FScopeLock ResultLock ( & ResultCriticalSection ) ;
2019-10-02 13:45:06 -04:00
Result = Factory - > NewResultShared < ESPMode : : ThreadSafe > ( ) ;
2019-08-05 20:07:14 -04:00
}
}
}
}
if ( Result . IsValid ( ) )
{
FScopeLock ResultLock ( & ResultCriticalSection ) ;
Result - > Serialize ( Ar ) ;
}
}
2019-10-31 12:19:40 -04:00
TUniquePtr < Audio : : IAnalyzerNRTSettings > UAudioAnalyzerNRT : : GetSettings ( const float InSampleRate , const int32 InNumChannels ) const
2019-08-05 20:07:14 -04:00
{
return MakeUnique < Audio : : IAnalyzerNRTSettings > ( ) ;
}
2019-10-31 12:19:40 -04:00
# if WITH_EDITOR
void UAudioAnalyzerNRT : : SetSettingsDelegate ( UAudioAnalyzerNRTSettings * InSettings )
{
if ( InSettings )
{
if ( AnalyzeAudioDelegateHandles . Contains ( InSettings ) )
{
// Avoid setting delegate more tha once
return ;
}
FDelegateHandle DelegateHandle = InSettings - > AnalyzeAudioDelegate . AddUObject ( this , & UAudioAnalyzerNRT : : AnalyzeAudio ) ;
if ( DelegateHandle . IsValid ( ) )
{
AnalyzeAudioDelegateHandles . Add ( InSettings , DelegateHandle ) ;
}
}
}
void UAudioAnalyzerNRT : : RemoveSettingsDelegate ( UAudioAnalyzerNRTSettings * InSettings )
{
if ( InSettings )
{
if ( AnalyzeAudioDelegateHandles . Contains ( InSettings ) )
{
FDelegateHandle DelegateHandle = AnalyzeAudioDelegateHandles [ InSettings ] ;
if ( DelegateHandle . IsValid ( ) )
{
InSettings - > AnalyzeAudioDelegate . Remove ( DelegateHandle ) ;
}
AnalyzeAudioDelegateHandles . Remove ( InSettings ) ;
}
}
}
# endif