In reviewing the Waveform Editor plugin I seem to have stumbled upon a convenient way to access PCM data. SampleBufferIO.cpp contains the classes FSoundWavePCMLoader and FSoundWavePCMWriter, which can be used to read raw PCM data and generate new PCM data for a SoundWave asset, in both the editor and at runtime.
Example of reading PCM data:
void UAudioAssetActions::LoadPCM(USoundWave* SoundWave)
{
if (!SoundWave) return;
Audio::FSoundWavePCMLoader SoundWaveLoader;
auto OnLoadedCallback = [](const USoundWave* LoadedSoundWave, const Audio::FSampleBuffer& SampleBuffer)
{
if (LoadedSoundWave)
{
// We can now read the raw PCM data in the SampleBuffer
}
};
SoundWaveLoader.LoadSoundWave(SoundWave, OnLoadedCallback, true);
}
You can also generate new PCM data and apply it to a new or existing sound wave asset:
void UAudioAssetActions::WritePCM(USoundWave* SoundWave)
{
if (!SoundWave) return;
Audio::FSoundWavePCMLoader SoundWaveLoader;
auto OnLoadedCallback = [](const USoundWave* LoadedSoundWave, const Audio::FSampleBuffer& SampleBuffer)
{
if (LoadedSoundWave)
{
Audio::FSoundWavePCMWriter SoundWaveWriter;
// Create a new buffer w/ PCM data from the loaded sound wave
Audio::TSampleBuffer NewBuffer = SampleBuffer;
// modify the PCM data in the new buffer, ex. using NewBuffer.Append();
auto OnGenerateSuccess = [](const USoundWave* GeneratedSoundWave)
{
if (GeneratedSoundWave)
{
UE_LOG(LogTemp, Warning, TEXT("Sound wave successfully generated"));
}
};
// Write the updated PCM data back to the SoundWave asset
SoundWaveWriter.BeginGeneratingSoundWaveFromBuffer(NewBuffer, const_cast<USoundWave*>(LoadedSoundWave), OnGenerateSuccess);
}
};
SoundWaveLoader.LoadSoundWave(SoundWave, OnLoadedCallback, true);
}
Here’s an example of some code I wrote that will append an existing sound wave asset with silence:
#include "AudioAssetActions.h"
#include "Sound/SampleBufferIO.h"
void UAudioAssetActions::AppendSilence(const TArray<UObject*> SelectedAssets, const float DurationInSeconds)
{
if (SelectedAssets.IsEmpty() || DurationInSeconds <= 0.f) return;
Audio::FSoundWavePCMLoader SoundWaveLoader;
for (UObject* Asset : SelectedAssets)
{
if (USoundWave* SoundWave = Cast<USoundWave>(Asset))
{
auto OnLoadedCallback = [DurationInSeconds, SoundWave](const USoundWave* LoadedSoundWave, const Audio::FSampleBuffer& SampleBuffer)
{
if (LoadedSoundWave)
{
Audio::FSoundWavePCMWriter SoundWaveWriter;
// original buffer properties
const float PCMSampleRate = SampleBuffer.GetSampleRate();
const int32 PCMChannels = SampleBuffer.GetNumChannels();
const int32 PCMSamples = SampleBuffer.GetNumSamples();
const int16* PCMData = SampleBuffer.GetData();
// Calculate # of samples from silence duration
const int32 SamplesToAdd = FMath::RoundToInt(PCMSampleRate * DurationInSeconds) * PCMChannels;
const int32 NewTotalSamples = PCMSamples + SamplesToAdd;
// array to hold the combined audio
TArray<int16> NewPCMData;
NewPCMData.SetNumUninitialized(NewTotalSamples);
// Copy the original audio data into new array
const int32 OriginalDataSizeBytes = PCMSamples * sizeof(int16);
FMemory::Memcpy(NewPCMData.GetData(), PCMData, OriginalDataSizeBytes);
// Fill the rest of the array with zeros to create silence
const int32 SilenceDataSizeBytes = SamplesToAdd * sizeof(int16);
FMemory::Memset(NewPCMData.GetData() + PCMSamples, 0, SilenceDataSizeBytes);
// Create buffer using combined data
const Audio::TSampleBuffer NewBuffer(NewPCMData.GetData(), NewTotalSamples, PCMChannels, PCMSampleRate);
auto OnGenerateSuccess = [SoundWave](const USoundWave* GeneratedSoundWave)
{
if (GeneratedSoundWave)
{
UE_LOG(LogTemp, Warning, TEXT("Sound wave successfully generated"));
// This is necessary otherwise check(Buffer->DecompressionState) will fail in AudioMixerBuffer.cpp
SoundWave->SetSoundAssetCompressionType(ESoundAssetCompressionType::BinkAudio);
}
};
// Write the updated PCM data back to the SoundWave asset
if (SoundWaveWriter.BeginGeneratingSoundWaveFromBuffer(NewBuffer, const_cast<USoundWave*>(LoadedSoundWave), OnGenerateSuccess))
{
UE_LOG(LogTemp, Error, TEXT("Successfully started generating sound wave from buffer"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to begin generating sound wave from buffer"));
}
}
};
SoundWaveLoader.LoadSoundWave(SoundWave, OnLoadedCallback, true);
}
}
}
Hopefully someone will find this useful 