GMS 2.3+ Audio queuing BUG(?) (simple project attached)

salyossy

Member
Hello, I believe there is a problem with the system of audio queuing.
To prove that check this really simple project (direct link): simple audio project

This is reall easy to understand, please do not skip. Here's the the complete code in the test project:


Create Event: create a short buffer, and then copy it 1000 times to a long buffer
GML:
sampleRate= 44100

//create short buffer
loop_Buff_short = buffer_create(36, buffer_fixed, 1)     
buffer_write(loop_Buff_short,buffer_s16,1167)
buffer_write(loop_Buff_short,buffer_s16,10621)
buffer_write(loop_Buff_short,buffer_s16,18161)
buffer_write(loop_Buff_short,buffer_s16,26286)
buffer_write(loop_Buff_short,buffer_s16,28006)
buffer_write(loop_Buff_short,buffer_s16,22229)
buffer_write(loop_Buff_short,buffer_s16,19146)
buffer_write(loop_Buff_short,buffer_s16,14471)
buffer_write(loop_Buff_short,buffer_s16,5814)
buffer_write(loop_Buff_short,buffer_s16,-207)
buffer_write(loop_Buff_short,buffer_s16,-4970)
buffer_write(loop_Buff_short,buffer_s16,-10348)
buffer_write(loop_Buff_short,buffer_s16,-18214)
buffer_write(loop_Buff_short,buffer_s16,-24407)
buffer_write(loop_Buff_short,buffer_s16,-24897)
buffer_write(loop_Buff_short,buffer_s16,-25675)
buffer_write(loop_Buff_short,buffer_s16,-24386)
buffer_write(loop_Buff_short,buffer_s16,-14635)

//create long buffer which is 1000 copies of the short one
loop_Buff_long = buffer_create(36000, buffer_fixed, 1)     
for (var i=0; i<=1000; i++)
{
    buffer_copy(loop_Buff_short, 0,    buffer_get_size(loop_Buff_short), loop_Buff_long,i*buffer_get_size(loop_Buff_short))
}

Event Key Press A: Queue 1000 copies of the SHORT buffer, and play it!
GML:
audio_queue_short = audio_create_play_queue(buffer_s16, sampleRate, audio_mono);

for (var i=1; i<=1000; i++)
{
    audio_queue_sound(audio_queue_short,loop_Buff_short,0 ,buffer_get_size(loop_Buff_short));
}

audio_play_sound(audio_queue_short, 0, false);

Event Key Press B: Queue only 1 copy of the LONG buffer, and play it!
GML:
audio_queue_long = audio_create_play_queue(buffer_s16, sampleRate, audio_mono);

audio_queue_sound(audio_queue_long,loop_Buff_long,0 ,buffer_get_size(loop_Buff_long));

audio_play_sound(audio_queue_long, 0, false);
You can clearly hear the difference when you press A (distorted sound), compared to when you press B (clean sound)
But it doesn't make any sense to me, since the process is similar.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I currently can't test the project (as I'm at work, taking a break and browsing the forum... :p ), but I can make a couple of suggestions. Not sure how relevant they will be to the issue however...

- First is the buffer alignment - shouldn't it be 2 byte aligned instead of 1, since you're only writing s16's to it?
- Compare the buffer_tell value at the end of writing the short buffer with the buffer size and see if there is a discrepancy. You might be better off using the buffer tell value instead of size if there is (there shouldn't be from what I can see, but it's a "just in case" check).
- With the first example using the short buffer queue, have you tried checking the async audio playback event to see that the buffers are being played correctly?
 

salyossy

Member
I really appreciate your willing to help.

1) Yes of course, i tried many types of alignments, there is no difference

2) I tried with buffer_tell, and the result is the same

3) My main goal is to use that with the async event, since i want to have control on the amount of buffers to be queued.
And this is what i did in the first place. Since i got this "distorted" sound, i began to search the root of the problem and this test project looks like a final step to recognize the problem, since it clearly points out to the source of the problem.

4) More useful info: if I combine both methods, meaning, taking the "long" buffer which sounds great, and queuing it multiple of times, then I can hear a small "pop" each time a new buffer get its turn. This makes me think that the buffers are not queued right. But my code looks ok to me
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Okay... I'll test this myself when I get home from work and see what I can find (assuming someone else doesn't come along first and solve the issue). I do have one last thing to ask though.... what happens if you use just 1 short sound in the queue? Or 10? Or 200? etc... Basically, do you get this distortion with less sounds?
 

Bart

WiseBart
I tried the test project in the original post. I couldn't hear a big difference between the two methods, though when pressing the A key (i.e. queueing 1000 short buffers and playing that) multiple times quickly I got a higher pitched sound from time to time. That doesn't happen when pressing the B key (i.e. queueing and playing the single long buffer that contains 1000 copies). Fixing that higher pitched sound can apparently be done by freeing the play queue using audio_free_play_queue before creating the new one:
GML:
/// Create Event
audio_queue_short = -1;

/// Key Press Event
if (audio_queue_short != -1) {
    audio_free_play_queue(audio_queue_short);
}
audio_queue_short = audio_create_play_queue(buffer_s16, sampleRate, audio_mono);
// More code here...
When I change the code to this the issue is gone. There does seem to be a slight delay between plays when pressing A quickly multiple times but I guess that can be explained by the fact that GM has to queue 1000 buffers every time while in the other case it just has to queue one long buffer that is ready to be played.

I'm not entirely sure how to explain the higher pitched sound though, since you're actually telling GM to create a new queue every time. A higher pitch would mean that more samples are being played in the same amount of time. I guess that might be explained by that previous audio queue's contents still being sent to the audio output and with GM then not realizing that the sound's length has also changed. Could perhaps something like that be going on in the background?

It should be possible to verify that kind of thing by adding some debug output to the async Audio Playback event. If you still get debug output in that event from the previous queue after creating a new one using audio_create_play_queue then you know something's going on that shouldn't be happening.

Hope this can help a bit :)
 

salyossy

Member
Thanks @Bart for taking the time to test it.

My intention was to make it as short as possible, and easy to follow, I'm aware of the need to free the resources, and the higher pitch you got with multiple "A plays", is definitely not the problem i am facing.
The difference between two methods occur at ONE press only. If you can't hear the difference, then, assuming it's not a hardware thing, and your speaker volume isn't too low to hear the difference, then I guess its a matter of expectation, the distortion i am talking about is very gentle, but has great influence on my work.

Both of the sounds are at the same pitch, but one of them has a tiny dirty sound, it's not clean sound.

@Nocturne ,I'm afraid i do not understand what you mean by saying "just 1 sound", what are you referring to?
I just have one simple buffer which I'm playing looped 1000 times, with two methods, which gives a very different output to my ears.. 🤷‍♂️
 

salyossy

Member
OK, so I attach here a direct link to an audio file, I redcorded them one ofter another,
first the "clean" (similar to a sine wave), and then the "distorted".

Link: audio result

I don't care about these specific results, as their purpose is only to illustrate the principle: There is a problem with the transition between the buffers!!

Now, if the buffers are long, then you can here a tiny tiny "pop" between them [which is not as a result of not caring to zero crossing. the samples where chosen carefuly to avoid that kind of 'pop']

And if the buffers are very short (like in my case), then milions of tiny 'pops' are joined and makes it distorted.

I believe this is a BUG, the buffers aren't queued perfectly ☹

(p.s. I reported a bug......)
 
Last edited:

salyossy

Member
I feel that so far I have not been able to convince you of the validity of my claim. To make the difference more clear to you, I made another audio ouput, where you can hear 4 times the 2 types of quailty alternating. Increase the speaker volume (not too much..) and you'll be able to hear the huge difference

altenating audio outputs

Code:
GML:
audio_queue = audio_create_play_queue(buffer_s16, sampleRate, audio_mono);

repeat (4)
{
    //distorted!
    for (var i=1; i<=3000; i++)
    {
        audio_queue_sound(audio_queue,loop_Buff_short,0 ,buffer_get_size(loop_Buff_short));
    }
   
    //clean!
    audio_queue_sound(audio_queue,loop_Buff_long,0 ,buffer_get_size(loop_Buff_long));
}

audio_queue_sound(audio_queue,loop_Buff_long,0 ,buffer_get_size(loop_Buff_long));
audio_play_sound(audio_queue, 0, false);
At this point I am close to loose hope, I only wish someone could confirm that he can understand my problem and confirm it.

Thank you for reading
 

GMWolf

aka fel666
OK, so I attach here a direct link to an audio file, I redcorded them one ofter another,
first the "clean" (similar to a sine wave), and then the "distorted".

Link: audio result

I don't care about these specific results, as their purpose is only to illustrate the principle: There is a problem with the transition between the buffers!!

Now, if the buffers are long, then you can here a tiny tiny "pop" between them [which is not as a result of not caring to zero crossing. the samples where chosen carefuly to avoid that kind of 'pop']

And if the buffers are very short (like in my case), then milions of tiny 'pops' are joined and makes it distorted.

I believe this is a BUG, the buffers aren't queued perfectly ☹
A pop between small buffers could be the system not keeping up with the audio: at 44100hz, and 18 samples per buffer you need to feed 2500 buffers per second, which may just be too many.
However the pops between larger buffers im unfamiliar with. i havent encountered this before. It could very well be that there is a bug somewhere in GM or the audio stack.

But as a general rule having such tiny audio buffers is not a great idea. Try using larger buffers 64, 128 or 512 samples, see if that helps.
if you are not dealing with real-time audio, then larger buffers are better.
If its real time audio you are wanting to do: a word of warning. The async audio events are not actually async. they get queued up and trigger syncronously with the game loop. That means that you cant get lower latency than 2x your frame time. Good luck.

p.s. Better to mention me in a topic rather than send me a pm.
p.p.s. It was years ago that i messed with audio buffers in GM. Idk how useful I can be...
 
Last edited:

salyossy

Member
Thank you @GMWolf , it means a lot.

I am dealing with real-time audio, and sometimes must use small buffers. At first place I got this result using async events.
Exactly as you said, I suspected that the 'blur' in the sound is a result of:
1)
could be the system not keeping up with the audio: at 44100hz, and 18 samples per buffer you need to feed 2500 buffers per second, which may just be too many.
OR
2)
The async audio events are not actually async. they get queued up and trigger syncronously with the game loop.
This is why i tested this without async, just queuing it in not-real time, and see if that is what causing the problem. And unfortunaly its not.
 

Bart

WiseBart
@salyossy The distortion is clearly audible in both audio recordings that you posted, it sounds like some sort of humming.
I cannot reproduce that issue here on my computer though. It doesn't occur for me. The thing that caught my attention earlier on was the changes in pitch, which doesn't seem to be your actual issue.
 

GMWolf

aka fel666
Most real time applications don't use buffers smaller than 64 samples. And even then it may be rough depending on hardware. Better to target 128 or 256.

As for the async event thing. I don't have a solution. It just kinda sucks...
 

salyossy

Member
@salyossy The distortion is clearly audible in both audio recordings that you posted, it sounds like some sort of humming.
I cannot reproduce that issue here on my computer though. It doesn't occur for me. The thing that caught my attention earlier on was the changes in pitch, which doesn't seem to be your actual issue.
@Bart
Surprisingly, your answer was actually very helpful ...
When you said it did not occur to you, when you where trying my test-project, this was actually the first time I realized that my assumption it could not be affected by the "hardware sound card" was a wrong assumption. I was pretty convinced there is no point in performing a test on another device.
So now I tried exporting it to my android, and you're right! The output of my phone makes no difference between the two methods of queuing: a uniform sound is heard throughout the process. What a suprise!

However, when I listen on the phone to a recording I made on the computer, (which, its important to mention, was a clean internal Line-In recording!), there is a significant difference. This means that there is a dependence on the audio processing, not the speaker output. I am really surprised. I do not know whether to be happy or sad, because it means things that sound great to me, May sound bad on another device. I can't take things like that in account when progamming.

One thing is still clear to me, there must be SOME difference between queuing small buffers and "queue" just one big buffer that is a "sum" of many small buffers, since my computer tells me this!


Most real time applications don't use buffers smaller than 64 samples. And even then it may be rough depending on hardware. Better to target 128 or 256.
Great info. Thanks
 
Last edited:

Roman P.

Member
If your app doesn't involve any live monitoring/interaction with the generated audio on the fly stick to a 256 buffer size.
Also your waveform is popping all over the place effectively creating harmonics. Rethink of how you're generating the waves. I know this one was hardcoded but doing it this way after few iteration the phase/pitch ends up completely out of whack.
 
Top