Sample rate bug that causes SamplePlayers to get "stuck" playing

I am using the C SDK version 1.12.3 on Windows.

In my project, I was using an array of SamplePlayers as a cache to allow me to play only a certain number of sounds at any given time. Whenever my play sample function is called, it would iterate through the array and use pd->sound->sampleplayer->isPlaying() to check if there are any SamplePlayers currently available. I am also using the rate parameter in playdate->sound->sampleplayer->play() to randomize the rate to add variety to the sound effects.

I noticed while running my game on both simulator and device that over time the number of sound effects slowly dropped off, until eventually no sound effects would play at all. The isPlaying() function was always returning true even though the sound effects were over.

After some testing, I found that there are certain values of rate that cause the SamplePlayer to get stuck. One example number I found is 0.9127f . This value will always cause isPlaying() to return true. I briefly tried to see if FilePlayers had the same issue, but at least it doesn't seem to happen using the value 0.9127f.

As it stands, isPlaying() is very unsafe to use if you are using any kind of randomized sample rate value. I guess I could get around it temporarily by having an array of "safe" sample rates and choose from those instead.

The samples I am using are mono ADPCM WAV files exported from Audacity. I will attach one of them to this post in case the problem is specific to the sample format.

Here is the smallest code example I could make to reproduce the issue:

#include "pd_api.h"

SamplePlayer * samplePlayer;
AudioSample * sample;

static int update(void* userdata);

#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t arg)
{
	if ( event == kEventInit )
    {
        samplePlayer = pd->sound->sampleplayer->newPlayer();
        sample = pd->sound->sample->load("sample");

        pd->sound->sampleplayer->setSample(samplePlayer, sample);
        // 0.9127f is one example of a bad rate value that causes the SamplePlayer to get stuck
        pd->sound->sampleplayer->play(samplePlayer, 1, 0.9127f);

		pd->system->setUpdateCallback(update, pd);
	}
	
	return 0;
}

static int update(void* userdata)
{
    PlaydateAPI *pd = userdata;

    pd->graphics->clear(kColorWhite);

    if(pd->sound->sampleplayer->isPlaying(samplePlayer)) {
        pd->graphics->drawText("Sample is playing", 17, kASCIIEncoding, 60, 120);
    } else {
        pd->graphics->drawText("Sample is not playing", 21, kASCIIEncoding, 60, 120);
    }

	return 1;
}

sample.zip (8.3 KB)

2 Likes

Thanks for catching this! A fix is in the queue. The problem is when it's decoded and played all the data there's still one sample left in the decode buffer but I wasn't checking for that before doing an early return, so it never finishes. As a workaround you can compare the sampleplayer's offset to its length.

pd->system->logToConsole("pos=%i len=%i",
    (int)(44100*pd->sound->sampleplayer->getOffset(samplePlayer)),
    (int)(44100*pd->sound->sampleplayer->getLength(samplePlayer)));

prints pos=16355 len=16356 when the player is stuck. I'm not sure if it'll always be one sample short, but I'm guessing that'll be the case.

3 Likes