Procedural Audio

Here is the source code for the 2 classes. If you add these to a project you should be able to then create the SoundNodeProceduralTest inside a Sound Cue and connect it’s output wire to the speaker, hit play cue, and here the tone that is created (plus the “click” that I’m trying to debug).



// File: SoundNodeProceduralTest.h
#pragma once

#include "Sound/SoundNode.h"
#include "SoundWaveProceduralTest.h"
#include "SoundNodeProceduralTest.generated.h"

UCLASS()
class AUDIOTESTPROJECT_API USoundNodeProceduralTest : public USoundNode
{
	GENERATED_UCLASS_BODY()

	// Volume of the sound [0-1]
	UPROPERTY(EditAnywhere, Category="Sound Source Properties")
	float Volume;

	// Frequency of the test sound [Hz]
	UPROPERTY(EditAnywhere, Category="Sound Source Properties")
	float Frequency;	// [Hz]

	USoundWaveProceduralTest *SoundWaveProcedural;

	// Begin USoundNode Interface

	virtual int32 GetMaxChildNodes() const override;
	virtual float GetDuration() override;
	virtual void ParseNodes( FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash, FActiveSound& ActiveSound, const FSoundParseParameters& ParseParams, TArray<FWaveInstance*>& WaveInstances ) override;
	virtual FString GetUniqueString() const override;
#if WITH_EDITOR
	virtual FString GetTitle() const override;	
#endif

protected:

	bool SoundWaveIsInitialized;

	void CreateSoundWaveStreaming();
};
// EndFile: SoundNodeProceduralTest.h

// BeginFile: SoundNodeProceduralTest.cpp

#include "AudioTestProject.h"
#include "SoundNodeProceduralTest.h"

USoundNodeProceduralTest::USoundNodeProceduralTest(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
, Volume(0.5f)
, Frequency(100.0f)
{
}

int32 USoundNodeProceduralTest::GetMaxChildNodes() const
{
	return 0;
}

float USoundNodeProceduralTest::GetDuration()
{
	return INDEFINITELY_LOOPING_DURATION;
}

void USoundNodeProceduralTest::ParseNodes(FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash, FActiveSound& ActiveSound, const FSoundParseParameters& ParseParams, TArray<FWaveInstance*>& WaveInstances)
{
	// method 1 : works "sort of" , but seems like the clip stops playing and restarts
	if(!SoundWaveIsInitialized)
	{
		CreateSoundWaveStreaming();
	}

	if(SoundWaveIsInitialized)
	{
		if(SoundWaveProcedural)
		{
			SoundWaveProcedural->Frequency = Frequency;
			SoundWaveProcedural->Volume = Volume;
			SoundWaveProcedural->Parse(AudioDevice, NodeWaveInstanceHash, ActiveSound, ParseParams, WaveInstances);
		}
	}
}


void USoundNodeProceduralTest::CreateSoundWaveStreaming()
{
	SoundWaveProcedural = NewObject<USoundWaveProceduralTest>();
	SoundWaveIsInitialized = true;
}

FString USoundNodeProceduralTest::GetUniqueString() const
{
	return TEXT("FOO UNIQUE STRING");
}

#if WITH_EDITOR

FString USoundNodeProceduralTest::GetTitle() const
{
	return TEXT("Procedural Sound Wave Source");
}
// EndFile: SoundNodeProceduralTest.cpp

// BeginFile:SoundWaveProceduralTest.h
#pragma once

#include "Sound/SoundWave.h"
#include "SoundWaveProceduralTest.generated.h"

//////////////////////////////////////////////////////////////////////////
/// USoundWaveProceduralTest :
///		A USoundWave that generates a single tone procedurally. This is
///		A test class that demonstrates the ability to create audio
///		procedurally (e.g. other algorithms could be used to generate
///		wind sounds etc.)
//////////////////////////////////////////////////////////////////////////
UCLASS()
class AUDIOTESTPROJECT_API USoundWaveProceduralTest : public USoundWave
{
	GENERATED_UCLASS_BODY()

	float Frequency;	// [Hz]

	// UObject interface

	virtual void Serialize(FArchive& Ar) override;
	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
	virtual SIZE_T GetResourceSize(EResourceSizeMode::Type Mode) override;

	// USoundWave interface

	virtual int32 GeneratePCMData(uint8* PCMData, const int32 SamplesNeeded) override;
	virtual int32 GetResourceSizeForFormat(FName Format) override;
	virtual FByteBulkData* GetCompressedData(FName Format) override;
	virtual void InitAudioResource(FByteBulkData& CompressedData) override;
	virtual bool InitAudioResource(FName Format) override;

private:

	// Time of the last sample that was copied to the PCMData buffer [s]
	float Time;

	// Time between two consecutive samples in the audio signal [s]
	float DeltaTime;

	// Angular frequency of the single tone [rad/s]
	float Omega;
};

#endif
// EndFile:SoundWaveProceduralTest.h

// BeginFile:SoundWaveProceduralTest.cpp
#include "AudioTestProject.h"
#include "SoundWaveProceduralTest.h"

USoundWaveProceduralTest::USoundWaveProceduralTest(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
	, Time(0.0f)
	, Frequency(100.0f)
{
	SampleRate = 44100;
	NumChannels = 1;
	Duration = INDEFINITELY_LOOPING_DURATION;
	bProcedural = true;
	bLooping = false;
	DeltaTime = 1.0f / SampleRate;    // time increment between samples [s]
}

int32 USoundWaveProceduralTest::GeneratePCMData(uint8* PCMData, const int32 SamplesNeeded)
{
	float TimeStart = Time;

	Omega = 2.0f * PI * Frequency;	// angular frequency [rad/s]

	for(int i = 0; i < SamplesNeeded; i++)
	{
		Time = TimeStart + i * DeltaTime;
		int32 a = FMath::RoundToInt( 65535.0f * (1.0f + FMath::Sin(Omega*Time)) * 0.5f);
		if(a > 65535)
		{
			a = 65535;
		}
		else if( a < 0)
		{
			a = 0;
		}
		uint16 ua = (uint16)a;
		PCMData[2*i] = ua;			// Low Byte
		PCMData[2*i+1] = ua >> 8;	// High Byte
	}

	Time = Time + SamplesNeeded * DeltaTime;

	int32 BytesProvided = SamplesNeeded * 2;
	return BytesProvided;
}

SIZE_T USoundWaveProceduralTest::GetResourceSize(EResourceSizeMode::Type Mode)
{
	return 0;
}

int32 USoundWaveProceduralTest::GetResourceSizeForFormat(FName Format)
{
	return 0;
}

void USoundWaveProceduralTest::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
	// should never be in asset registry
	check(false);
}

FByteBulkData* USoundWaveProceduralTest::GetCompressedData(FName Format)
{
	return NULL;
}

void USoundWaveProceduralTest::Serialize(FArchive& Ar)
{
	// do not call the USoundWave version of serialize
	USoundBase::Serialize(Ar);
}

void USoundWaveProceduralTest::InitAudioResource(FByteBulkData& CompressedData)
{
	// should never be pushing compressed data
	check(false);
}

bool USoundWaveProceduralTest::InitAudioResource(FName Format)
{
	//nothing to be done to initialize
	return true;
}
// EndFile:SoundWaveProceduralTest.cpp