Godot Version
3.6.1.stable.mono.official
Question
Hello,
I’m trying to synthesize real time audio of a car engine following the general guide of this guy: https://youtu.be/MrIAw980iYg?t=1473
In his explanation he says he generates frames according to the time it takes for a full cycle of the base frequency to complete, but in Godot we have “get_frames_available()” which if I understand correctly tells us how many frames we need to provide, so I guess the first question would be what am I supposed to do? Just ignore the advice from the video and only fill the specific frames? or is it ok that I fill more than available frames and the audio processing will somehow take care of it? so the more general question is how does this buffer work? I understand that I’m writing to it and the audio device is reading from it, but how does this all keep in sync with game time, for example if I have a 0.5 sec buffer and the frames available lets say give me 0.1 sec of frames but the dt of process was less than that that means the audio and the game time will be out of sync?
Sorry for the messy question, I guess this shows you the mess I got in my head about this 
You can generate your PCM to a separate buffer at whatever size you like, and feed that into the audio stream at whatever speed the audio stream wants. You don’t have to feed the stream directly from the generator.
What do you mean exactly could you show a basic code example? Note that I can’t generate all audio in advance I need to do it in real time since it depends on the rpm, I tried generating enough frames for 2 cycles but it looks like much of the time it’s more than “availableFrames” so I’m not sure what to do about that
You can generate into a PackedByteArray or whatever and then copy it.
The idea is, your generator can generate as much as you like, but generate it into a separate buffer that’s just there as a bucket to hold your audio until it’s needed. Pour that bucket into the audio stream at whatever rate the audio stream can handle it, and have the generator keep an eye on the bucket and refill it whenever necessary.
Right, but If I generate more than I need I think the sound will lag behind the game? for example if I’m accelerating from 3500 to 4000 rpm at 3700 I might accidently hear the sound of 3600, no? Am I missing something?
Well, only generate as far ahead as you need to. You can just feed directly into the stream if you like, but if your generator wants to make more than the stream can eat, you can put a buffer between them. The generator generates to the buffer, and you can copy from the buffer to the stream as slowly or quickly as the stream needs.
That also potentially lets you move the generator to a separate thread, where it won’t be eating CPU time the game wants to use.
How does it look implementation wise, should I keep some kind of ring buffer or something? Where I would generate the audio into and then every time process is called I check if there is enough data that wasn’t yet read in the buffer and if not fill it? And I should just make sure it’s big enough to keep let’s say 2 * (amount of samples I need for the lowest frequency I’m going to use) ?
That really depends on the details of what you’re doing. You can use a ring buffer, or you can just use a regular buffer and refill it as needed. The size of buffer you need depends on how much data your generator wants to generate vs. how much audio the stream wants to consume.
The easy way to do this is something like:
#pseudocode
func stream_needs_data(amount: int):
if buffer.available_data >= amount:
buffer.copy_to_stream(stream, amount)
return
if buffer.available_data:
amount -= buffer.available_data
buffer.copy_to_stream(stream, buffer.available_data)
generator.make_data(buffer) # Refill the buffer...
buffer.copy_to_stream(stream, amount)
You could simplify that and put it in a loop if make_data() might make less than the stream wants in a single run:
#pseudocode
func stream_needs_data(amount: int):
while amount:
var a = max(amount - buffer.available_data, 0)
buffer.copy_to_stream(stream, buffer.available_data)
amount = a
generator.make_data(buffer)