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:
    you know to help them find their checkbook lol
  • BigOnYa @ BigOnYa:
    Gta4 was NewYork or Chicago, gta5 is California
  • K3Nv2 @ K3Nv2:
    Damn camera phones catching crimes
  • Psionic Roshambo @ Psionic Roshambo:
    Always some women screaming... You break a few thousand dollars worth of crap in someone's living room and they scream lol
  • Psionic Roshambo @ Psionic Roshambo:
    lol Ken yeah the things I did as a kid I am soooo glad those didn't exist
  • K3Nv2 @ K3Nv2:
    If someone breaks my $20 TV stand their nose is getting broke
  • Psionic Roshambo @ Psionic Roshambo:
    Psi would be doing like 300-3,000 years in prison lol
  • K3Nv2 @ K3Nv2:
    Have some in closet allegations?
  • Psionic Roshambo @ Psionic Roshambo:
    Someone tried to pull a gun on me once, they reached into the couch thinking I wouldn't notice, quick kick to the arm snapping it between the wrist and elbow broke like a swing lol I reached into the couch to see what was in there, cool a free .380 lol had to hit him with it a few times to remind him not to do it again lol
  • Psionic Roshambo @ Psionic Roshambo:
    He had the 20K he owed the very next day it was a miracle lol
  • Psionic Roshambo @ Psionic Roshambo:
    Psi was a bad bad man at one point lol
  • BigOnYa @ BigOnYa:
    We need a GTA based on your life, or at least put you in 6 as a character.
  • Psionic Roshambo @ Psionic Roshambo:
    I don't think people would believe 10% of the things I have done lol thank god...
  • Psionic Roshambo @ Psionic Roshambo:
    I have a giant check list of impossible things, and I haven't done them all yet lol
  • K3Nv2 @ K3Nv2:
    @Psionic Roshambo, Was the pot farmer in San andreas
  • Psionic Roshambo @ Psionic Roshambo:
    I tell people I wrestled a 5 foot alligator and they get this smile like this guy is full of shit lol the reality is I am sad it got away.... I wanted a pet alligator lol
  • BigOnYa @ BigOnYa:
    You live in Florida, so I believe it, you guys are crazy.
  • Psionic Roshambo @ Psionic Roshambo:
    At the time I would have probably fed it people lol
  • Psionic Roshambo @ Psionic Roshambo:
    Seriously cocaine not even once lol
  • BigOnYa @ BigOnYa:
    Not even once, but 100's of times
    +2
  • Psionic Roshambo @ Psionic Roshambo:
    My girlfriend at the time, she had me stay up with her all night because some how the crazy bitch had spent like 12 hours snorting 2 8 balls, didn't use any water (gotta clean your nose) so she had so much crusted in her nose I was sure she was gonna blow up her heart. I mean this was the stuff right off the boat so absolutely pure. ugghh so annoying
  • Psionic Roshambo @ Psionic Roshambo:
    Also doing like 320 dollars worth of coke in half a day lol damn it
  • Psionic Roshambo @ Psionic Roshambo:
    hmmm 360 even lol
  • Psionic Roshambo @ Psionic Roshambo:
    Well I was getting a discount so 320 is probably right
    Psionic Roshambo @ Psionic Roshambo: Well I was getting a discount so 320 is probably right