Homebrew [Q] NDSP with multiple sounds playing?

LeifEricson

Coming Soon™
OP
Member
Joined
Jun 22, 2012
Messages
234
Trophies
0
Age
28
Location
New York, USA
Website
www.youtube.com
XP
544
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,045
Trophies
1
XP
3,109
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
28
Location
New York, USA
Website
www.youtube.com
XP
544
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,045
Trophies
1
XP
3,109
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
28
Location
New York, USA
Website
www.youtube.com
XP
544
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
  • ZeroT21 @ ZeroT21:
    the music you heard while growing up will still be the most memorable, but sounds like dogshit listening through studio quality headwear
  • BigOnYa @ BigOnYa:
    I like gangster grandpa rap
  • K3Nv2 @ K3Nv2:
    You label me
  • ZeroT21 @ ZeroT21:
    i hate meself turning into audiophile addict
  • K3Nv2 @ K3Nv2:
    At least you didn't turn into a mj type of phile
  • ZeroT21 @ ZeroT21:
    ruined me childhood memories
  • ZeroT21 @ ZeroT21:
    mj's music didn't get ruined luckily
  • K3Nv2 @ K3Nv2:
    Just the tabloids ruined him
  • ZeroT21 @ ZeroT21:
    his vocals were just that gud
  • BigOnYa @ BigOnYa:
    He was an awesome dancer, nobody can deny that.
  • ZeroT21 @ ZeroT21:
    no comment on dat part

    :rolleyes:
  • BigOnYa @ BigOnYa:
    Ken just upset he wasn't molested by him
  • K3Nv2 @ K3Nv2:
    Vote for trump
  • BigOnYa @ BigOnYa:
    I'm voting for Kanye. Yeet Yeet mofo.
  • K3Nv2 @ K3Nv2:
    Until info gets leaked Kanye was at a diddy party
    +1
  • BigOnYa @ BigOnYa:
    Wasn't Trump, Kanye, and Diddy all friends? What a freak show that would be.
  • K3Nv2 @ K3Nv2:
    Probably in the woods some place
    +1
  • Domestica @ Domestica:
    A surprising amount of mods on. Something about to happen?
  • BigOnYa @ BigOnYa:
    Its Bantober....Mods are preparing.
  • Veho @ Veho:
    It's Bansgiving.
    +1
  • Jayro @ Jayro:
    Subscribe to my OnlyBans. <3
  • ZeroT21 @ ZeroT21:
    I only have 1 nut to offer
    Xdqwerty @ Xdqwerty: Hi