gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > Streaming Sound

#55325 - GPFerror - Tue Sep 27, 2005 7:26 pm

Need some help with how to do this or some examples please.

right now I am calling my function that generates a sound buffer on the arm9 with a set size and then calling playGenericSound on the buffer, right now I have no syncing up being done and the calcsoundbuffer and playsound functions im sure are being called before or late therefore sounding like crap. How can I calculate the buffersize so its in sync?

I was thinking of somekind of ringbuffer but dont know how to implement this on the DS.

I understand the general concept but not the implementation. I'v been looking through different released source code of different projects but there is usually so much other code that it makes it difficult to pick out and use what I need.

So anyone give me code, how to calculate buffersize based off timer/interupts ect on both arm7 and arm9 . and how to sync it up.

Thanks,
Troy(GPF)

#55375 - headspin - Wed Sep 28, 2005 2:28 am

GPF: This is for FrodoDS right? Did you get the files I sent to your e-mail? I was trying to set up a timer on the ARM7 that will store an IPC variable of the current play position of the buffer. From that the ARM9 can calculate when to send the next buffer chunk. Basing that on the Win32 implementation of sound in Frodo, it should theoretically work.

Unfortunately I couldn't get timers working at all using the new libnds, whereas the older ndslib was no problem.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#55377 - GPFerror - Wed Sep 28, 2005 2:33 am

actually its for something else because I knew you were working on FrodoDS but I didnt get your email could you send it again please. Or send me a link by pm and I will download it.

Thanks

#55398 - DekuTree64 - Wed Sep 28, 2005 6:18 am

The way I've done it before is by playing a looping buffer on ARM7, and using hardware timers on ARM9 to keep track of where it is in the buffer. The only tricky thing is getting the sound channel and the timers started at close to the same time.
Make sure the channel starts a little sooner though, so you never fill past where it currently is.

The actual ring buffering is just a matter of splitting up a load of samples if you're going to hit the end before it's done.

For example, say your buffer is 1000 samples long, your current position is 900, and you need to mix 500 samples. 900 + 500 would go past 1000, so it needs split up. Your first batch will be
bufferLength - bufferPos = 1000 - 900 = 100 samples.
After that's done, set the position back to 0, and since you've mixed 100 samples so far, you need to mix 400 more to get to your total 500. So mix them, update the buffer position to 400, and you're done.


For the sample counting, you can use a timer that triggers an interrupt every x samples, and in the interrupt update a variable telling how many samples need to be mixed. Then in your game loop or VBlank or something, mix however many samples have accumulated in that variable, and reset it to 0.

If I remember right, sound channel timers run at half the frequency of the CPU hardware timers, so set your counting timer's overflow period to 2*soundChannelTimerPeriod*x, where x is the number of samples you want to wait between each interrupt (I used 256).
The actual REG_TMxD value will be 65536 - period.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#55460 - gladius - Wed Sep 28, 2005 6:19 pm

Check out http://forum.gbadev.org/viewtopic.php?p=45741 for some sample code to do this. For the arm9, all I did was send a FIFO message from the arm7 when the mixing needed to be done and do the mixing on the arm9 then. Works a treat.

#55465 - GPFerror - Wed Sep 28, 2005 7:31 pm

Here is the code that im currently using on the arm9 side , EmulateLine() is called in a loop for each rasterline. How would I convert it to stay in sync? Really appreciate the help and hope that this message can be used by others since there seems to be little documentation on using sound streaming at this time.

I have gone through gladius's soundmixer forum post and DekuTree64 ModplayerDS code and many other sound code and Im understanding what its doing but im lost on how I should implement it for the code below.

Thanks,
Troy(GPF)

Code:

  const unsigned TOTAL_RASTERS = 0x138;
  const unsigned SCREEN_FREQ = 50;
  const int SAMPLE_BUF_SIZE = 0x138*2;
  const uint32 SAMPLE_FREQ = 11025;

void DigitalRenderer::init_sound(void)
{
  sndbufsize = SAMPLE_BUF_SIZE;
  sound_buffer = new int16[sndbufsize];
  int sample_in_ptr;
  ready = true;
}

  setGenericSound (SAMPLE_FREQ,64,64,1);

void DigitalRenderer::EmulateLine(void)
{
  if (ready)
  {
    static int divisor = 0;
    static int to_output = 0;
    static int buffer_pos = 0;

    if (!ready)
   return;

   sample_buf[sample_in_ptr] = volume;
   sample_in_ptr = (sample_in_ptr + 1) % SAMPLE_BUF_SIZE;

    /*
     * Now see how many samples have to be added for this line
     */
    divisor += SAMPLE_FREQ;
    while (divisor >= 0)
       divisor -= TOTAL_RASTERS*SCREEN_FREQ, to_output++;

    /*
     * Calculate the sound data only when we have enough to fill
     * the buffer entirely.
     */
     
    if ((buffer_pos + to_output) >= sndbufsize) {
   int datalen = sndbufsize - buffer_pos;
   to_output -= datalen;

                //creates the soundbuffer to be played
   calc_buffer(sound_buffer + buffer_pos, datalen*2);

                //play the sound
   playGenericSound(sound_buffer, sndbufsize*2);
   buffer_pos = 0;
    } 
  }
}

#55491 - GPFerror - Wed Sep 28, 2005 9:30 pm

gladius wrote:
Check out http://forum.gbadev.org/viewtopic.php?p=45741 for some sample code to do this. For the arm9, all I did was send a FIFO message from the arm7 when the mixing needed to be done and do the mixing on the arm9 then. Works a treat.


So on the arm7 is send a fifo(whats in it? bufferposition?) maybe a code snip would help me.

Then within my emulateline function I check something in the fifo skip if more data is not needed or call my calc_buffer(sound_buffer,length) with a length from the fifo? or do I still do my default length? Also do I call playGenericSound or just set some value in the fifo to the sndbuffer?

thanks,
Troy

#55587 - gladius - Thu Sep 29, 2005 6:30 pm

On the arm7 side right after I set up the channels to play, I do this which is just copying over the mixed data in the arm7 play buffers and sending off a command to the arm9 to mix.
Code:

        for (int i = 0; i < MIXBUFSIZE; i++) {
            playBuffer[soundCursor + i] = SPC_IPC->playBuffer[i];
            playBuffer[soundCursor + i + (MIXBUFSIZE * 2)] = SPC_IPC->playBuffer[i + MIXBUFSIZE];
        }
        if (IPC_FIFO_CR & IPC_FIFO_ERROR) IPC_FIFO_CR |= IPC_FIFO_SEND_CLEAR;
        SendArm9Command(ARM9COMMAND_UPDATE_APU);

The on the arm9 side I do this in my irq handler:
Code:

    if (flags & IRQ_FIFO_NOT_EMPTY) {
        // Command recieved from arm7
        do {
            u32 command = IPC_FIFO_RECIEVE;

            switch (command) {
            case ARM9COMMAND_UPDATE_APU:
                const int cyclesToExecute = spcCyclesPerSec / (MIXRATE / MIXBUFSIZE);
                for (int i = 0; i < cyclesToExecute / 32; i++) {
                    DspEmulateOneSample(((u16*)SPC_IPC->playBuffer) + i);
                }
                break;
            }
        } while (!(IPC_FIFO_CR & IPC_FIFO_RECV_EMPTY));

        IF = IRQ_FIFO_NOT_EMPTY;
    }

DspEmulateOneSample just sticks the current sample in at playBuffer[i] which is a buffer stored in shared memory.

#56173 - GPFerror - Thu Oct 06, 2005 9:51 pm

gladius wrote:
On the arm7 side right after I set up the channels to play, I do this which is just copying over the mixed data in the arm7 play buffers and sending off a command to the arm9 to mix.
Code:

        for (int i = 0; i < MIXBUFSIZE; i++) {
            playBuffer[soundCursor + i] = SPC_IPC->playBuffer[i];
            playBuffer[soundCursor + i + (MIXBUFSIZE * 2)] = SPC_IPC->playBuffer[i + MIXBUFSIZE];
        }
        if (IPC_FIFO_CR & IPC_FIFO_ERROR) IPC_FIFO_CR |= IPC_FIFO_SEND_CLEAR;
        SendArm9Command(ARM9COMMAND_UPDATE_APU);

The on the arm9 side I do this in my irq handler:
Code:

    if (flags & IRQ_FIFO_NOT_EMPTY) {
        // Command recieved from arm7
        do {
            u32 command = IPC_FIFO_RECIEVE;

            switch (command) {
            case ARM9COMMAND_UPDATE_APU:
                const int cyclesToExecute = spcCyclesPerSec / (MIXRATE / MIXBUFSIZE);
                for (int i = 0; i < cyclesToExecute / 32; i++) {
                    DspEmulateOneSample(((u16*)SPC_IPC->playBuffer) + i);
                }
                break;
            }
        } while (!(IPC_FIFO_CR & IPC_FIFO_RECV_EMPTY));

        IF = IRQ_FIFO_NOT_EMPTY;
    }

DspEmulateOneSample just sticks the current sample in at playBuffer[i] which is a buffer stored in shared memory.



what is MIXBUFSIZE set to? also is their timing involved on the arm9 side or can I continue to call my sound update buffer, but now it would check the fifo first before actually generating a new buffer?

thanks,
Troy

#56339 - gladius - Sat Oct 08, 2005 1:40 am

MIXBUFSIZE is set to however big you want your mix buffer to be. The arm9 is called whenever it needs to update the next MIXBUFSIZE samples. So that is when you should optimally do your mixing.

Edit: If you want to see this happening in practice and not pseudo-code, check out the pocketspc source (http://pocketspc.pocketheaven.com).

#59455 - GPFerror - Tue Nov 01, 2005 10:34 pm

ok looked through the source and im still confused at where to start, right now my code is arm9 only. So I need to switch to using arm7 too.

Below is my arm9 code which is called in a loop since its an emulator how should I change the arm9 code to stream the sound?

Code:
const unsigned TOTAL_RASTERS = 0x138;
  const unsigned SCREEN_FREQ = 50;
  const int SAMPLE_BUF_SIZE = 0x138*2;
  const uint32 SAMPLE_FREQ = 11025;

void DigitalRenderer::init_sound(void)
{
  sndbufsize = SAMPLE_BUF_SIZE;
  sound_buffer = new int16[sndbufsize];
  int sample_in_ptr;
  ready = true;
}

  setGenericSound (SAMPLE_FREQ,64,64,1);

void DigitalRenderer::EmulateLine(void)
{
  if (ready)
  {
    static int divisor = 0;
    static int to_output = 0;
    static int buffer_pos = 0;

    if (!ready)
   return;

   sample_buf[sample_in_ptr] = volume;
   sample_in_ptr = (sample_in_ptr + 1) % SAMPLE_BUF_SIZE;

    /*
     * Now see how many samples have to be added for this line
     */
    divisor += SAMPLE_FREQ;
    while (divisor >= 0)
       divisor -= TOTAL_RASTERS*SCREEN_FREQ, to_output++;

    /*
     * Calculate the sound data only when we have enough to fill
     * the buffer entirely.
     */
     
    if ((buffer_pos + to_output) >= sndbufsize) {
   int datalen = sndbufsize - buffer_pos;
   to_output -= datalen;

                //creates the soundbuffer to be played
   calc_buffer(sound_buffer + buffer_pos, datalen*2);

                //play the sound
   playGenericSound(sound_buffer, sndbufsize*2);
   buffer_pos = 0;
    } 
  }
}

#59497 - LiraNuna - Wed Nov 02, 2005 6:49 am

Off topic: What emulator is it?

#59538 - GPFerror - Wed Nov 02, 2005 2:42 pm

the c64 emulator Frodo

#59592 - Dark Knight ez - Wed Nov 02, 2005 8:43 pm

But you said, and I quote:
Quote:
actually its for something else because I knew you were working on FrodoDS

;)

#59603 - GPFerror - Wed Nov 02, 2005 9:24 pm

Dark Knight ez wrote:
But you said, and I quote:
Quote:
actually its for something else because I knew you were working on FrodoDS

;)


:) yeah that has changed now since I havent the slightest clue on how to do sound streaming for anything on the ds yet

#59673 - gladius - Thu Nov 03, 2005 6:38 am

The general architecture I chose for streaming sound requires a custom arm7 binary. It sets the sound system up as normal to play a looped buffer, which is essentially a timer IRQ that plays the new buffers.

Then when that timer irq occurs on the arm7 side, it plays the current buffer, and sets the toBeMixed buffer to the next one. It then sends a FIFO message to the arm9 saying "hey, i just started started playing your samples, start mixing the ones for next time right now".

The arm9 then recieves this message and goes ahead and does the mixing into shared ram at the toBeMixed buffer location.

Then the next time the irq happens on the arm7 it plays the toBeMixed buffer and sets the toBeMixed buffer to the old location. This cycle is repeated forever.