Homebrew [Q] NDSP with multiple sounds playing?

LeifEricson

Coming Soon™
OP
Member
Joined
Jun 22, 2012
Messages
234
Trophies
0
Age
27
Location
New York, USA
Website
www.youtube.com
XP
534
Country
United States
I'm trying to fix some audio glitches in my program. Basically, every couple of times it plays a sound, it glitches a bit before playing another sound. I can tell it's data leftover in the buffer because it's a glitchy version of what played before, so I'm guessing this has something to do with the way I implemented it, I'm only using 1 data buffer and 1 waveBuf. The function itself flushes buffers though and appears to handle that stuff. I know for a fact that's probably wrong, but I can't find good example code.

I'm not very familar with the ndsp system so I did find an example and copied it (I know, that's probably the reason why it's buggy), can someone tell me what I'm doing wrong?

Code:
// Global wave buffer
ndspWaveBuf waveBuf;
// Data buffer
u8* data = NULL;

...

int playWav(string path, int channel = 1, bool toloop = true) {
    u32 sampleRate;
    u32 dataSize;
    u16 channels;
    u16 bitsPerSample;
  
    ndspSetOutputMode(NDSP_OUTPUT_STEREO);
    ndspSetOutputCount(2); // Num of buffers
  
    // Reading wav file
    FILE* fp = fopen(path.c_str(), "rb");
  
    if(!fp)
    {
        printf("Could not open the example.wav file.\n");
        return -1;
    }
  
    char signature[4];
  
    fread(signature, 1, 4, fp);
  
    if( signature[0] != 'R' &&
        signature[1] != 'I' &&
        signature[2] != 'F' &&
        signature[3] != 'F')
    {
        printf("Wrong file format.\n");
        fclose(fp);
        return -1;
    }
  
    fseek(fp, 40, SEEK_SET);
    fread(&dataSize, 4, 1, fp);
    fseek(fp, 22, SEEK_SET);
    fread(&channels, 2, 1, fp);
    fseek(fp, 24, SEEK_SET);
    fread(&sampleRate, 4, 1, fp);
    fseek(fp, 34, SEEK_SET);
    fread(&bitsPerSample, 2, 1, fp);
  
    if(dataSize == 0 || (channels != 1 && channels != 2) ||
        (bitsPerSample != 8 && bitsPerSample != 16))
    {
        printf("Corrupted wav file.\n");
        fclose(fp);
        return -1;
    }
 
    // Allocating and reading samples
    data = static_cast<u8*>(linearAlloc(dataSize));
    fseek(fp, 44, SEEK_SET);
    fread(data, 1, dataSize, fp);
    fclose(fp);
  
    fseek(fp, 44, SEEK_SET);
    fread(data, 1, dataSize, fp);
    fclose(fp);
  
    // Find the right format
    u16 ndspFormat;
  
    if(bitsPerSample == 8)
    {
        ndspFormat = (channels == 1) ?
            NDSP_FORMAT_MONO_PCM8 :
            NDSP_FORMAT_STEREO_PCM8;
    }
    else
    {
        ndspFormat = (channels == 1) ?
            NDSP_FORMAT_MONO_PCM16 :
            NDSP_FORMAT_STEREO_PCM16;
    }
  
    ndspChnReset(channel);
    ndspChnSetInterp(channel, NDSP_INTERP_NONE);
    ndspChnSetRate(channel, float(sampleRate));
    ndspChnSetFormat(channel, ndspFormat);
  
    // Create and play a wav buffer
    std::memset(&waveBuf, 0, sizeof(waveBuf));
  
    waveBuf.data_vaddr = reinterpret_cast<u32*>(data);
    waveBuf.nsamples = dataSize / (bitsPerSample >> 3);
    waveBuf.looping = toloop;
    waveBuf.status = NDSP_WBUF_FREE;
  
    DSP_FlushDataCache(data, dataSize);
  
    ndspChnWaveBufAdd(channel, &waveBuf);
  
    return ((dataSize / (bitsPerSample >> 3)) / sampleRate); // Return duration in seconds, for debugging purposes
  
}

...

    // Somewhere in main...
    if (condition1) {
        ndspChnWaveBufClear(1);
        playWav("sdmc:/3ds/appname/data/song.wav",1,false); // This plays often
    }

    if(keys & KEY_Y) playWav("sdmc:/3ds/appname/data/song.wav",1,false); // This plays occasionally
 
Last edited by LeifEricson,

elhobbs

Well-Known Member
Member
Joined
Jul 28, 2008
Messages
1,044
Trophies
1
XP
3,034
Country
United States
I'm trying to fix some audio glitches in my program. Basically, every couple of times it plays a sound, it glitches a bit before playing another sound. I can tell it's data leftover in the buffer because it's a glitchy version of what played before, so I'm guessing this has something to do with the way I implemented it, I'm only using 1 data buffer and 1 waveBuf. The function itself flushes buffers though and appears to handle that stuff. I know for a fact that's probably wrong, but I can't find good example code.

I'm not very familar with the ndsp system so I did find an example and copied it (I know, that's probably the reason why it's buggy), can someone tell me what I'm doing wrong?

Code:
// Global wave buffer
ndspWaveBuf waveBuf;
// Data buffer
u8* data = NULL;

...

int playWav(string path, int channel = 1, bool toloop = true) {
    u32 sampleRate;
    u32 dataSize;
    u16 channels;
    u16 bitsPerSample;
 
    ndspSetOutputMode(NDSP_OUTPUT_STEREO);
    ndspSetOutputCount(2); // Num of buffers
 
    // Reading wav file
    FILE* fp = fopen(path.c_str(), "rb");
 
    if(!fp)
    {
        printf("Could not open the example.wav file.\n");
        return -1;
    }
 
    char signature[4];
 
    fread(signature, 1, 4, fp);
 
    if( signature[0] != 'R' &&
        signature[1] != 'I' &&
        signature[2] != 'F' &&
        signature[3] != 'F')
    {
        printf("Wrong file format.\n");
        fclose(fp);
        return -1;
    }
 
    fseek(fp, 40, SEEK_SET);
    fread(&dataSize, 4, 1, fp);
    fseek(fp, 22, SEEK_SET);
    fread(&channels, 2, 1, fp);
    fseek(fp, 24, SEEK_SET);
    fread(&sampleRate, 4, 1, fp);
    fseek(fp, 34, SEEK_SET);
    fread(&bitsPerSample, 2, 1, fp);
 
    if(dataSize == 0 || (channels != 1 && channels != 2) ||
        (bitsPerSample != 8 && bitsPerSample != 16))
    {
        printf("Corrupted wav file.\n");
        fclose(fp);
        return -1;
    }

    // Allocating and reading samples
    data = static_cast<u8*>(linearAlloc(dataSize));
    fseek(fp, 44, SEEK_SET);
    fread(data, 1, dataSize, fp);
    fclose(fp);
 
    fseek(fp, 44, SEEK_SET);
    fread(data, 1, dataSize, fp);
    fclose(fp);
 
    // Find the right format
    u16 ndspFormat;
 
    if(bitsPerSample == 8)
    {
        ndspFormat = (channels == 1) ?
            NDSP_FORMAT_MONO_PCM8 :
            NDSP_FORMAT_STEREO_PCM8;
    }
    else
    {
        ndspFormat = (channels == 1) ?
            NDSP_FORMAT_MONO_PCM16 :
            NDSP_FORMAT_STEREO_PCM16;
    }
 
    ndspChnReset(channel);
    ndspChnSetInterp(channel, NDSP_INTERP_NONE);
    ndspChnSetRate(channel, float(sampleRate));
    ndspChnSetFormat(channel, ndspFormat);
 
    // Create and play a wav buffer
    std::memset(&waveBuf, 0, sizeof(waveBuf));
 
    waveBuf.data_vaddr = reinterpret_cast<u32*>(data);
    waveBuf.nsamples = dataSize / (bitsPerSample >> 3);
    waveBuf.looping = toloop;
    waveBuf.status = NDSP_WBUF_FREE;
 
    DSP_FlushDataCache(data, dataSize);
 
    ndspChnWaveBufAdd(channel, &waveBuf);
 
    return ((dataSize / (bitsPerSample >> 3)) / sampleRate); // Return duration in seconds, for debugging purposes
 
}

...

    // Somewhere in main...
    if (condition1) {
        ndspChnWaveBufClear(1);
        playWav("sdmc:/3ds/appname/data/song.wav",1,false); // This plays often
    }

    if(keys & KEY_Y) playWav("sdmc:/3ds/appname/data/song.wav",1,false); // This plays occasionally
The ndsp service interface as implemented by ctrulib uses a separate thread to send requests to the ndsp service. Essentially ctrulib calls update a channel state structure that the ndsp thread monitors for changes. When changes are found they are sent to the ndsp service. So I believe you have two issues - reusing the buffer for multiple sounds and sending a stop and a play on the same channel/buffer combination without letting the thread process the stop first.
One solution would be to use a separate buffer for each channel and picking an unused channel to start a new sound. Keep in mind that picking an unused channel can suffer from the same issue - if you don't wait long enough for the thread to see a play request the ctrulib calls to see if a channel is in use will return false.
 

LeifEricson

Coming Soon™
OP
Member
Joined
Jun 22, 2012
Messages
234
Trophies
0
Age
27
Location
New York, USA
Website
www.youtube.com
XP
534
Country
United States
The ndsp service interface as implemented by ctrulib uses a separate thread to send requests to the ndsp service. Essentially ctrulib calls update a channel state structure that the ndsp thread monitors for changes. When changes are found they are sent to the ndsp service. So I believe you have two issues - reusing the buffer for multiple sounds and sending a stop and a play on the same channel/buffer combination without letting the thread process the stop first.
One solution would be to use a separate buffer for each channel and picking an unused channel to start a new sound. Keep in mind that picking an unused channel can suffer from the same issue - if you don't wait long enough for the thread to see a play request the ctrulib calls to see if a channel is in use will return false.

Is there some sort of svc sleep function that can wait for those changes to process? And as for different buffers for each channel, would I only need separate waveBufs or also separate data buffers as well? Thank you for your help.
 

elhobbs

Well-Known Member
Member
Joined
Jul 28, 2008
Messages
1,044
Trophies
1
XP
3,034
Country
United States
svcSleepThread(20000) seems to be long enough, but I think you may be better off calculating the end time when you play a sound and tracking it yourself. if you are starting and stopping a lot of sounds then the sleeps will add up. And yes you need separate waveBufs and data buffers. Both are used the entire time the sound is playing.
 

LeifEricson

Coming Soon™
OP
Member
Joined
Jun 22, 2012
Messages
234
Trophies
0
Age
27
Location
New York, USA
Website
www.youtube.com
XP
534
Country
United States
svcSleepThread(20000) seems to be long enough, but I think you may be better off calculating the end time when you play a sound and tracking it yourself. if you are starting and stopping a lot of sounds then the sleeps will add up. And yes you need separate waveBufs and data buffers. Both are used the entire time the sound is playing.

Okay, I've redone the sound system based on some other code I found and your advice. I've put some error checking, and I'm getting valid returns, but... the sound doesn't play. sourceIsPlaying is True while the sound should be playing, and goes to False after what seems to be the correct duration. There's just no audio output. See the code below. Again, I'm really inexperienced with DSP, so I appreciate the hand holding.

Code:
typedef struct {
    source_type type;
   
    float rate;
    u32 channels;
    u32 encoding;
    u32 nsamples;
    u32 size;
    char* data;
    bool loop;
    int audiochannel;

    float mix[12];
    ndspInterpType interp;
} source;

bool channelList[24];

int getOpenChannel() {

    for (int i = 0; i <= 23; i++) {
        if (!channelList[i]) {
            channelList[i] = true;
            return i;
        }
    }

    return -1;

}

const char *sourceInit(source *self, const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (file) {
        bool valid = true;
        char buff[8];

        // Master chunk
        fread(buff, 4, 1, file); // ckId
        if (strncmp(buff, "RIFF", 4) != 0) valid = false;

        fseek(file, 4, SEEK_CUR); // skip ckSize

        fread(buff, 4, 1, file); // WAVEID
        if (strncmp(buff, "WAVE", 4) != 0) valid = false;

        // fmt Chunk
        fread(buff, 4, 1, file); // ckId
        if (strncmp(buff, "fmt ", 4) != 0) valid = false;

        fread(buff, 4, 1, file); // ckSize
        if (*buff != 16) valid = false; // should be 16 for PCM format

        fread(buff, 2, 1, file); // wFormatTag
        if (*buff != 0x0001) valid = false; // PCM format

        u16 channels;
        fread(&channels, 2, 1, file); // nChannels
        self->channels = channels;
       
        u32 rate;
        fread(&rate, 4, 1, file); // nSamplesPerSec
        self->rate = rate;

        fseek(file, 4, SEEK_CUR); // skip nAvgBytesPerSec

        u16 byte_per_block; // 1 block = 1*channelCount samples
        fread(&byte_per_block, 2, 1, file); // nBlockAlign

        u16 byte_per_sample;
        fread(&byte_per_sample, 2, 1, file); // wBitsPerSample
        byte_per_sample /= 8; // bits -> bytes

        // There may be some additionals chunks between fmt and data
        fread(&buff, 4, 1, file); // ckId
        while (strncmp(buff, "data", 4) != 0) {
            u32 size;
            fread(&size, 4, 1, file); // ckSize

            fseek(file, size, SEEK_CUR); // skip chunk

            int i = fread(&buff, 4, 1, file); // next chunk ckId

            if (i < 4) { // reached EOF before finding a data chunk
                valid = false;
                break;
            }
        }

        // data Chunk (ckId already read)
        u32 size;
        fread(&size, 4, 1, file); // ckSize
        self->size = size;

        self->nsamples = self->size / byte_per_block;

        if (byte_per_sample == 1) self->encoding = NDSP_ENCODING_PCM8;
        else if (byte_per_sample == 2) self->encoding = NDSP_ENCODING_PCM16;
        else return "unknown encoding, needs to be PCM8 or PCM16";

        if (!valid) {
            fclose(file);
            return "invalid PCM wav file";
        }

        self->audiochannel = getOpenChannel();
        self->loop = false;

        // Read data
        if (linearSpaceFree() < self->size) return "not enough linear memory available";
        self->data = (char*)linearAlloc(self->size);

        fread(self->data, self->size, 1, file);


        fclose(file);
    }
    else return "file not found";
    return "ok";
}

int sourcePlay(source *self) { // source:play()

    if (self->audiochannel == -1) {
        return -1;
    }

    ndspChnWaveBufClear(self->audiochannel);
    ndspChnReset(self->audiochannel);
    ndspChnInitParams(self->audiochannel);
    ndspChnSetMix(self->audiochannel, self->mix);
    ndspChnSetInterp(self->audiochannel, self->interp);
    ndspChnSetRate(self->audiochannel, self->rate);
    ndspChnSetFormat(self->audiochannel, NDSP_CHANNELS(self->channels) | NDSP_ENCODING(self->encoding));

    ndspWaveBuf* waveBuf = (ndspWaveBuf*)calloc(1, sizeof(ndspWaveBuf));

    waveBuf->data_vaddr = self->data;
    waveBuf->nsamples = self->nsamples;
    waveBuf->looping = self->loop;

    DSP_FlushDataCache((u32*)self->data, self->size);

    ndspChnWaveBufAdd(self->audiochannel, waveBuf);

    return self->audiochannel;
}

bool sourceIsPlaying(source *self) { // source:isPlaying()
   return ndspChnIsPlaying(self->audiochannel);
}

...

// In main...
source* correct = new source;
initRes = sourceInit(correct, "sdmc:/3ds/orchestrina/data/OOT_Song_Correct.wav"); // Valid result

...

if (condition) playRes = sourcePlay(ocarina5); // Valid return result, sound doesn't play...
 

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
  • Psionic Roshambo @ Psionic Roshambo:
    hmmm 360 even lol
  • Psionic Roshambo @ Psionic Roshambo:
    Well I was getting a discount so 320 is probably right
  • BigOnYa @ BigOnYa:
    That is cheap, I used to pay $100 for a tine.
  • Psionic Roshambo @ Psionic Roshambo:
    Tine? One gram?
  • BigOnYa @ BigOnYa:
    Sixteenth
  • Psionic Roshambo @ Psionic Roshambo:
    Also it was literally out of a kilo when I got it off the boat so absolutely pure
  • Psionic Roshambo @ Psionic Roshambo:
    Holy shiz that's a lot
    +1
  • Psionic Roshambo @ Psionic Roshambo:
    I was getting 3.5 Grams for 320 could have stepped on it and doubled my money easy lol
    +1
  • BigOnYa @ BigOnYa:
    I'd be afraid to it nowdays, my heart would explode prob. I just stick beers n buds nowdays.
  • Psionic Roshambo @ Psionic Roshambo:
    I would get to drive from tarpon springs to like Miami a thousand bucks lol do that twice a week and back in 92 that was good money
  • Xdqwerty @ Xdqwerty:
    @BigOnYa,
    @Psionic Roshambo what are you guys talking about?
  • Psionic Roshambo @ Psionic Roshambo:
    Blew it on women and muscle cars lol
    +1
  • BigOnYa @ BigOnYa:
    @Xdqwerty Hamster food, its pricey nowadays to keep PCs running.
    +2
  • Psionic Roshambo @ Psionic Roshambo:
    I don't do anything except cigarettes and gotta stop eventually lol
    +1
  • BigOnYa @ BigOnYa:
    I'd do shrooms again if could find, and I was outside camping/fishing, and had a cooler full of beer.
    +1
  • Psionic Roshambo @ Psionic Roshambo:
    I wouldn't mind some LSD, laughing until my face hurt sounds fun lol
    +1
  • BigOnYa @ BigOnYa:
    You ever try soaper powder/qauludes? I did once and like a dumbass drank beer on top of taking, I woke up laying in my backyard in the pouring rain, it knocked me out. I have not seen it around in many many years.
    +1
  • Psionic Roshambo @ Psionic Roshambo:
    No never tried a lot of things but never that lol
  • Psionic Roshambo @ Psionic Roshambo:
    I did pass out one time on a floor after taking a bunch of Ambien lol thought it would help me sleep and did it lol
  • Psionic Roshambo @ Psionic Roshambo:
    Girlfriend was working at a pharmacy and stole like 500 of them, was and still is the biggest pill bottle I have ever seen lol
  • K3Nv2 @ K3Nv2:
    Ativan is pretty legit
    +1
  • Psionic Roshambo @ Psionic Roshambo:
    The last time I had to take something to help me sleep, I was prescribed Trazadone it was pretty OK to be honest.
  • Psionic Roshambo @ Psionic Roshambo:
    Not something I need at all these days, doing a lot better lol
    Psionic Roshambo @ Psionic Roshambo: Not something I need at all these days, doing a lot better lol