Homebrew libnds : soundResume() actually restarts the whole track?

NotImpLife

Member
OP
Newcomer
Joined
Mar 9, 2021
Messages
17
Trophies
0
Age
22
Website
github.com
XP
170
Country
Romania
Hi! I have trouble with getting a soundtrack paused/resumed when clicking on a button. Here is the (oversimplified) code:

Code:
int sound = soundPlaySample(soundData, SoundFormat_ADPCM, dataSize, freq, 100, 64, false, 0);
soundPause(sound);  // <-- it pauses after (let's say) 3 seconds
soundResume(sound); // <-- replays from the beginning intead of resuming from the position it paused

My question: Is this the way soundPause/soundResume actually works? If so, how can I effectively resume the song from the same position it paused?
I would appreciate any clues and recommendations.


UPDATE:

After a long brainstorm I think I actually found some way to "pause" the song. The idea is to set the frequency to 0.

Code:
int sound = soundPlaySample(soundData, SoundFormat_ADPCM, dataSize, freq, 100, 64, false, 0);
soundSetFreq(sound, 0);    // <-- this pauses
soundSetFreq(sound, freq); // <-- this resumes

This seems to do exactly what I was looking for.

UPDATE 2:

Well this partially does the job. Letting the sound "paused" for a long time results in losing its sync. So the issue is still open.
 
Last edited by NotImpLife,

NotImpLife

Member
OP
Newcomer
Joined
Mar 9, 2021
Messages
17
Trophies
0
Age
22
Website
github.com
XP
170
Country
Romania
IDK NDS programming, and it appears there should be a simpler way, but you could capture the current play time when pause is hit (to a variable) and start the sound again from that play time.

Yeah, I was thinking of that too, but haven't found a way to get the playing offset at a certain time.
 
  • Like
Reactions: KiiWii

NotImpLife

Member
OP
Newcomer
Joined
Mar 9, 2021
Messages
17
Trophies
0
Age
22
Website
github.com
XP
170
Country
Romania
I had a long and hard time thinking about this problem, but I finally dealt with it. I want to share my result in order to make the life easier to anyone who somehow would face the same problem in the future...

KiiWii's idea is perfectly fine, and it can be easily implemented in an NDS homebrew using a timer and converting the sound to an uncompressed format (for our ease to precisely tell which offset should we replay the sountrack from when resuming).

Here is a quick code mockup to prove the idea:

C:
#include <nds.h>
#include "bgm_bin.h" // ADPCM sound file

const int freq = 8192; // frequence of the track

u16 decoded[4*bgm_bin_size]; // PCM buffer

u32 sndcnt=0;                       //  sound counter
void timerCallBack() { sndcnt++; }  // (increments each {freq} Hertz)
// sndcnt is linear to the time passed since the track has started
// and it should be also proporitional to the number of bytes in the PCM
// track buffer that have been played so far.


// format converter
// src    : track in ADPCM format
// srclen : size of track in bytes
// dest   : result track in PCM format (note that its size is 4*srclen)
void ADPCMtoPCM(const u8* src, int srclen, void* dest);

int main(void)
{   
    powerOn(POWER_ALL_2D);
    soundEnable();
    // convert the track to 16-bit signed PCM
    ADPCMtoPCM(bgm_bin,bgm_bin_size,decoded);
    // start the track...
    int bgid = soundPlaySample(decoded, SoundFormat_16Bit, 4*bgm_bin_size, freq, 100, 64, false, 0);
    // ... and the timer that ticks each 1/{freq} seconds
    timerStart(0, ClockDivider_1, TIMER_FREQ(freq), timerCallBack);
   
    while(1)
    {
        scanKeys();
        int keys = keysDown();
       
        if(keys & KEY_UP) // Up <- pause the track
        {
            soundPause(bgid); // both the timer and the sound
            timerPause(0);    // sndcnt stops incrementing
           
        }
        else if(keys & KEY_DOWN) // Down <- resume the track
        {
            soundKill(bgid);                  // instead of soundResume()
            bgid = soundPlaySample(           // and restart the track from a different offset
                decoded + sndcnt,             // skip the bits already played
                SoundFormat_16Bit,           
                4*bgm_bin_size-sndcnt,        // be careful to correctly give the total size of the "trimmed" track
                freq, 100, 64, false,0);
            timerUnpause(0);                  // resume timer (sndcnt starts incrementing)
        }
        else if(keys & KEY_A) // A <- restart the track
        {
            // just kill the track & relaunch it
            soundKill(bgid); 
            bgid = soundPlaySample(decoded,SoundFormat_16Bit,4*bgm_bin_size,freq,100,64,false,0);
            // reset timer & sound  counter
            timerStart(0, ClockDivider_1, TIMER_FREQ(freq), timerCallBack);
            sndcnt=0;
        }
        swiWaitForVBlank();
    }
    return 0;
}


// decoder source: https://github.com/miso-xyz/PPMLib/blob/c7548bf4cdb0e368af552c71a45eb9f96f2e3385/PPMLib/Extensions/AdpcmDecoder.cs#L46-L119
// Credit: RinLovesYou
const int IndexTable[] =
{
    -1, -1, -1, -1, 2, 4, 6, 8,
    -1, -1, -1, -1, 2, 4, 6, 8,
};

const int ADPCM_STEP_TABLE[] =
{
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
    19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
    50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767, 0
};
       
int NumClamp(int n, int l, int h)
{
    if (n < l) return l;   
    if (n > h)    return h;   
    return n;
}

void ADPCMtoPCM(const u8* src, int srclen, void* dest)
{
    u16* dst = (u16*)dest;
   
    int srcPtr = 0;
    int dstPtr = 0;
    int sample = 0;
    int step_index = 0;
    int predictor = 0;
    bool lowNibble = true;
   
    while (srcPtr < srclen)
    {
        // switch between high and low nibble each loop iteration
        // increments srcPtr after every high nibble
        if (lowNibble)       
            sample = src[srcPtr] & 0xF;       
        else       
            sample = src[srcPtr++] >> 4;       
        lowNibble = !lowNibble;
        int step = ADPCM_STEP_TABLE[step_index];
        int diff = step >> 3;

        if ((sample & 1) != 0)       
            diff += step >> 2;       
        if ((sample & 2) != 0)       
            diff += step >> 1;       
        if ((sample & 4) != 0)       
            diff += step;
        if ((sample & 8) != 0)       
            diff = -diff;       
        predictor += diff;
        predictor = NumClamp(predictor, -32768, 32767);
        step_index += IndexTable[sample];
        step_index = NumClamp(step_index, 0, 88);
        dst[dstPtr++] = (short)predictor;
    }       
}

Note that this approach doesn't cover the case of streaming data from a file in filesystem. This code is meant to work on small ADPCM files which have been previously buffered in RAM.

Hope this may be of some good use to someone, sometime.
 
General chit-chat
Help Users
    NoobletCheese @ NoobletCheese:    YOU   DON'T MATTER  GIVE UP