USoundWave::GeneratePCMData() question...

Apologies if this is not the correct place to post this question, but I couldn’t find an audio specific forum. Anyhow, I’ve been trying to create my own SoundCue node and I’m having a little trouble understanding how I should properly use the USoundWave::GeneratePCMData(uint8* PCMData, const int32 SamplesNeeded) method. I hope that someone here can help me.

The first thing that confuses me is the fact that *PCMData is an uint8. I assume that samples are 16bit, so how is one expected to work with 8bit ints in the range of 0-255? Is there some kind of low-level bit magic I need to use here?

Also, the method returns an int32 but the docs don’t say what that should be. From looking through source code it appears that this should be the number of bytes. Am I right to assume this should be SamplesNeeded*2 if we’re dealing with 16bit audio samples?

And finally, the docs state:

/** 
 * This is only for DTYPE_Procedural audio. Override this function.
 *  Put SamplesNeeded PCM samples into Buffer. If put less,
 *  silence will be filled in at the end of the buffer. If you
 *  put more, data will be truncated.
 *  Please note that "samples" means individual channels, not
 *  sample frames! If you have stereo data and SamplesNeeded
 *  is 1, you're writing two SWORDs, not four!
 */

There’s a reference to “samples” but the only mention of samples is in the SamplesNeeded int32, which I assume provides the PCM buffer size. So what is this reference to “samples” and the number of audio channels? Any clarification or insights are most welcome!

1 Like

It’s just a uint8* since that’s the most generic way to represent a memory buffer - a pointer to the raw bytes. If you’re going to set the values in-place within the buffer, you can just cast the pointer:



auto samples = (int16*)PCMData;
samples[n] = ...;


Yep, specifically it’s the number of bytes of valid samples that you’ve filled in. So it would be SamplesNeeded * sizeof(int16) if you provided all the requested samples.

It’s just referring to it’s use of the word ‘samples’ in the first sentence of the comment, and defining what was meant by that.

Thank you for your reply, this helps a lot! So I went ahead and tried to output a simple waveform, only something still ain’t right. In my class constructor I create an array of int16 that holds a simple waveform:



	for (int i = 0; i< 4096; i++)
	{
		sineWave* = FMath::RoundToInt(65535 * (FMath::Sin((220.f * 2.f * PI * i) / 44100.f)));
		UE_LOG(ModuleLog, Warning, TEXT("Sample:%d"), sineWave*);
	}

I can tell from the output that it’s creating a simple sine wave with amp -/+35000. Then in the GeneratePCMData() method I try to read this waveform and fill the PCMData buffer with it:


int32 USoundWaveProceduralTest::GeneratePCMData(uint8* PCMData, const int32 SamplesNeeded)
{
	auto sample = (uint16*)PCMData;
	for (int i = 0; i < SamplesNeeded; i++, sampleIndex++)
	{
		if (sampleIndex >= 4096)
			sampleIndex = 0;			

		PCMData* = sineWave[sampleIndex];	
	}

	int32 BytesProvided = SamplesNeeded*sizeof(uint16);
	return BytesProvided;
}

When I start my SoundCue I get a choppy signal that don’t sound much like a sine wave. Can you see what the problem is? I’m hoping to create some simple sound synthesis objects but so far I can’t even output a simple waveform :stuck_out_tongue:

You’ve assigned the value to the byte.
This


PCMData* = sineWave[sampleIndex];

should be


sample* = sineWave[sampleIndex];

Also, I’m not sure but I think you should be using int16 instead of uint16. Regardless you need to change your calculation since 65535 * Sin(x) has a range of -65535, +65535]. Try changing to using int16 and then use INT16_MAX * Sin(x).

D’oh! That was stupid of me! Thanks to your help I can now at least hear a sine wave. The only issue is that it is not continuous. My updated processing block looks like this:


{
	auto sample = (int16*)PCMData;
	for (int i = 0; i < SamplesNeeded; i++, sampleIndex++)
	{
		if (sampleIndex >= 4096)
			sampleIndex = 0;			

		sample* = sineWave[sampleIndex];	
	}

	int32 BytesProvided = SamplesNeeded*sizeof(int16);
	return BytesProvided;
}

Can you spot an issue? It sounds like I’m not filling every sample in the buffer and the last few in each buffer are being zeroed. I can’t think why that might be happening? “SamplesNeeded” is 8192 each time, and going by the code above I’m filling each sample? Or?

Looks like it should be okay. To be honest, I don’t actually know anything about how this function gets called and the way the audio code works in general.

Maybe try validating/outputting your sineWave array somehow to ensure that the data is correct?

You’re right. I was making a balls of generating my sine wave data. Wow. I really managed to post some impressively awful code in such a short message thread! Thanks again for the help, I really appreciate it.

This is correct. Integer audio samples are always signed, with the exception of 8 bit PCM which is normally unsigned (and there is 32 bit floating point, but that’s a different story). For UE4 this obviously always means int16, since it doesn’t support any other bit rates :wink:

As kamrann already pointed out the uint16 should be int16 (and the assignment to the sample) - other than that, you are making a pointer alias of a different type (‘sample’ points to the same location as the uint8* PCMData buffer). I’m never too sure on this, but I think that is undefined behaviour in C(++). In practice it will most likely work fine unless you are using insane compiler settings.

Personally what I do in GeneratePCMData() is allocate a buffer of the chosen data type (so int16) and loop over the samples to fill that. Then I simply do FMemory::Memcpy(PCMData, Buffer, <num bytes in buffer>). If for some reason you are underflowing (you can’t provide as many samples as the audio device is requesting), you can Memzero the remaining bytes of the output buffer.

It’s completely fine to cast a pointer like this. It’s just the address of a block of memory, the type of pointer is merely a compile-time concept. So long as you understand what is stored at the memory location, you’re free to access it through whatever pointer type you like if it simplifies your code. So in this case there’s no point allocating and copying, you’re just adding performance overhead.

I’m well aware of the extra copy involved, but undefined behaviour scares me way more than that. Over 99% of the time in my GeneratePCMData function is actually spent on audio decompression, not copying 8 KB of samples every 200ms. Here’s some fun reading material about GCC’s insane strict aliasing behaviour… I think I’ll pass on that. Fortunately MSVC and Clang still seem to do ‘what you’d expect’, but undefined behaviour is still undefined behaviour and it can change with any new compiler version.

Thanks everyone. It’s all making lots of sense now. It as that cast that was catching me out.

I should probably ask in a new thread, but does anyone know if it is possible to expose a method from my USoundNode derived class so that I can trigger it from a Blueprint? Right now I can create a reference to my SoundCue in an AudioComponent, and I can tweak parameters, but is it possible to actually call any of my node classe public method’s? I can mange without, but it would be much nicer if I didn’t have to! Thanks again for all the feedback.

You’re right that in this case the overhead is going to be negligible. Still, there is no aliasing going on here, at least in the sense relevant to compiler optimization issues. We’re just casting the pointer to a more convenient type, not accessing the same address through multiple aliased pointers. “Here’s the address of a bunch of bytes. Treat them as int16s”. That’s it, the compiler can’t screw that up.

Sounds like you just want to expose a function to blueprint?


UFUNCTION(BlueprintCallable, Category = "My Category")
void MyFunction();


Cool. I thought that macro was just for actors.

[edit] I just took a look at this now. I add the following to my USoundNode derived class:


	UFUNCTION(BlueprintCallable, Category = "SoundEvent")
	void sendSoundEvent(FString soundEvent);
	{
		UE_LOG(LogTemp, Warning, TEXT("sound event"));
	}

It doesn’t show in the SoundCue Editor. Nor can I seem to access it through the AudioComponent that references my SoundCue class in my main level blueprint. This is probably a total newbie problem. I’m genuinely going through all the documentation I can find. Im sure the solution will become clear soon.

[found it!]…