[Q] NDSP with multiple sounds playing?

Discussion in '3DS - Homebrew Development and Emulators' started by LeifEricson, Jul 9, 2016.

  1. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
    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, Jul 9, 2016
  2. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    811
    299
    Jul 28, 2008
    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.
     
  3. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
    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.
     
  4. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    811
    299
    Jul 28, 2008
    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.
     
  5. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
    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...
     
  6. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    811
    299
    Jul 28, 2008
    United States
    Did you call ndspInit to initialize the Dsp service?
     
  7. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
  8. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
    Just an update, I was never able to figure it out.
     
  9. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    811
    299
    Jul 28, 2008
    United States
    I am not sure if you should be calling these each time you start a sound
    ndspChnWaveBufClear(self->audiochannel);
    ndspChnReset(self->audiochannel);
    ndspChnInitParams(self->audiochannel);
     
  10. LeifEricson
    OP

    LeifEricson Coming Soon™

    Member
    214
    166
    Jun 22, 2012
    United States
    New York, USA
    Hmm, good point. I'll look into that later.