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.

ASM > Streaming music from audio file in EFS

#167526 - headspin - Tue Mar 17, 2009 1:58 pm

I'm trying to stream some music for a game using the EFS to break the 4 MB memory limit of the DS. So far I have got the basic streaming working but every time the buffer is refilled there is an annoying click sound. Is there are bug in the code or perhaps streaming from the ROM is not fast enough in the timer? Any suggestions would be appreciated.

Code from main.s

Code:
   ldr r0, =strInGame            @ Read the path to the file
   bl playAudio               @ Play the sound

mainLoop:

   bl swiWaitForVBlank
   b mainLoop

strInGame:
   .asciz "/InGame.raw"


Here is the audioStream.s

Code:
   #define BUFFER_SIZE      4096
   #define AUDIO_FREQ      22050

   .arm
   .align
   .text
   .global initAudioStream
   .global playAudio
   .global audioStreamTimer1
   
initAudioStream:

   stmfd sp!, {r0-r1, lr}
   
   ldr r0, =TIMER0_DATA
   ldr r1, =SOUND_FREQ(AUDIO_FREQ) * 2
   strh r1, [r0]
   
   ldr r0, =TIMER0_CR
   ldr r1, =(TIMER_ENABLE | TIMER_DIV_1)
   strh r1, [r0]
   
   ldr r0, =TIMER1_DATA
   ldr r1, =(0x10000 - (BUFFER_SIZE / 2));
   strh r1, [r0]

   ldr r0, =TIMER1_CR
   ldr r1, =(TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_CASCADE)
   strh r1, [r0]
   
   ldmfd sp!, {r0-r1, pc}                        @ Return
   
   @ ---------------------------------------------
   
playAudio:

   stmfd sp!, {r0-r2, lr}

   @ r0 = string pointer to music
   
   mov r1, r0                                 @ Move string pointer
   ldr r0, =fileName                           @ Read address of fileName
   ldr r2, =256                              @ Size == 256
   bl memcpy                                 @ Copy filename to fileName
      
   ldr r0, =fileName
   ldr r1, =(BUFFER_SIZE / 2)
   ldr r2, =fileSize                           @ Read fileSize address
   
   bl readFileSize
   
   str r0, [r2]                              @ Write the filesize to it
   
   ldr r0, =fileName
   ldr r1, =buffer
   ldr r2, =bufferPos
   ldr r2, [r2]
   ldr r3, =(BUFFER_SIZE / 2)                     @ Read the buffer size
   
   bl readFileStream                           @ Read the next buffer of audio file
   bl playBuffer
   
   ldmfd sp!, {r0-r2, pc}                        @ restore registers and return

   @ ---------------------------------------------

playBuffer:
   stmfd sp!, {r0-r1, lr}

   ldr r0, =IPC_SOUND_LEN(0)                     @ Get the IPC sound length address
   ldr r1, =BUFFER_SIZE                        @ buffer size
   str r1, [r0]                              @ Write the buffer size
   
   ldr r0, =IPC_SOUND_DATA(0)                     @ Get the IPC sound data address
   ldr r1, =buffer                              @ Get the sample address
   str r1, [r0]                              @ Write the value
   
   ldmfd sp!, {r0-r1, pc}                        @ restore registers and return
   
   @ ---------------------------------------------
   
audioStreamTimer1:

   stmfd sp!, {r0-r6, lr}
   
   ldr r0, =fileName
   ldr r1, =buffer
   ldr r2, =bufferPos
   ldr r2, [r2]
   ldr r3, =(BUFFER_SIZE / 2)                  @ Read the buffer size
   
   ldr r4, =backBuffer
   ldr r5, [r4]
   cmp r5, #0
   moveq r5, #1
   addeq r1, r3
   movne r5, #0
   str r5, [r4]
   
   bl readFileStream                           @ Read the next buffer of audio file

   bl DC_FlushAll                           @ Flush the cache
   
   ldr r0, =bufferPos                           @ Read the bufferPos address
   ldr r1, [r0]                              @ Read the bufferPos value
   ldr r2, =fileSize                           @ Read the file size address
   ldr r2, [r2]                              @ Read the fileSize value
   ldr r3, =(BUFFER_SIZE / 2)                     @ Read the buffer size
   add r1, r3                                 @ Add buffer size to bufferPos
   str r1, [r0]                              @ Write value back to bufferPos
   
   
   ldmfd sp!, {r0-r6, pc}                        @ Return
   
   @ ---------------------------------------------

   .data
   .align
   
backBuffer:
   .word 0
   
bufferPos:
   .word 0
   
fileSize:
   .word 0
   
   .section .bss
   
fileName:
   .space 256
   
buffer:
   .space BUFFER_SIZE
   
   .pool
   .end


and the readFile.c routines for reading from the EFS

Code:
FILE *pFileStream = NULL;
   
int readFileStream(char *fileName, unsigned char *pBuffer, int pos, int size)
{
   size_t result;
   
   if(pFileStream == NULL)
      pFileStream = fopen(fileName, "rb");
   
   if (pFileStream == NULL)
      return 0;
   
   result = fseek(pFileStream, pos, SEEK_SET);
   
   if(result != 0)
   {
      fclose(pFileStream);
      return 0;
   }

   result = fread(pBuffer, 1, size, pFileStream);
   
   if(result != size)
   {
      fclose(pFileStream);
      return 0;
   }

   return 1;
}

int readFileSize(char *fileName)
{
   struct stat fileStat;
   size_t result;
   
   result = stat(fileName, &fileStat);
   
   if(result != 0)
      return 0;
      
   return fileStat.st_size;
}

_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game


Last edited by headspin on Fri Mar 20, 2009 4:03 pm; edited 10 times in total

#167529 - elhobbs - Tue Mar 17, 2009 2:19 pm

16k does sound a little big to be reading all at once. also, it looks like you are restarting a new sound each chunk - it hard for me to tell as I am not exactly an asm expert. I think most of the streaming audio examples tend to use a circular buffer with the sound playing as a loop. then the update function loads the next section of the audio file to the correct position in the circular buffer - making sure not touch near where the current audio is playing.

#167530 - headspin - Tue Mar 17, 2009 3:01 pm

I think your right elhobbs but I can't find an example that reads directly from EFS or doesn't use FIFO. I have made the buffer 2048 and now only play the sound with a loop instead of triggering play every time the timer is called.

So I guess I will have to implement a ring buffer and keep it filled just in front of the play cursor. Perhaps someone could shed some more light or give an example of implementation?
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#167531 - elhobbs - Tue Mar 17, 2009 3:36 pm

as_lib, maxmod - either one of these should provide the needed info. I do not think it matters if they use efs or fifo (though I think as_lib can be built to use efs) - you just need to look at the parts involving filling the buffers at the right time and in the right place.

I think the typical approach is to create a buffer and split it into two sections. one section is currently playing and the other section needs to be loaded. I believe some care needs to be taken to make sure everyting is cache aligned (4 byte /32bit aligned).

#167532 - headspin - Tue Mar 17, 2009 4:37 pm

Thanks elhobbs you've been a great help. Splitting the buffer into two sections has improved the quality alot. Infact at times it's perfect but I'm guessing the play cursor eventually gets to a point where its right between the buffer swap and starts causing the clicking again and eventually is goes out of sync and sounds right again.

I've updated my first post with the code changes I've made. Any ideas on how to compensate for this syncing issue?
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#167533 - elhobbs - Tue Mar 17, 2009 5:14 pm

maybe set the timer to a higher frequency then load a little after the sound goes to the next buffer, but well before it switches to the next buffer. so, when your update function is called calculate the position were the sound is currently playing and compare it to the next load position and the already loaded position. if the current position is > next load position and next load position > loaded position then read more data and update the loaded position

#167551 - headspin - Wed Mar 18, 2009 4:41 am

Changing to a frequency of 32000 seems to have fixed the sync issues. But it does add a meg and a bit to the file size for the music. I would prefer to go with 22050. There must be a value close to that frequency that will work without going out of sync. I would love to know if it's possible.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#167556 - elhobbs - Wed Mar 18, 2009 1:42 pm

I was not suggesting that the frequency for the sound be changed. I was suggesting that the timer be changed to a higher frequency. so, that it is called maybe 4 times while playing each half of the buffer. on the 1,3,4 calls do nothing. on the 2 call load more data.

in any case I think the issue may be with your code for setting up TIMER0_DATA
Code:
   ldr r1, =(TIMER_FREQ(AUDIO_FREQ) & 0xFFFE)
this code is modifying the value calulated by TIMER_FREQ. Why not just use
Code:
   ldr r1, =TIMER_FREQ(AUDIO_FREQ)

#167592 - headspin - Thu Mar 19, 2009 2:15 pm

It doesn't make a difference using "& 0xFFFE" or not I just saw it in another example. I have removed it from the current code anyway.

I understood what you meant but the problem is it's getting out of sync with the timer. Changing to use a frequency of 32000 solves the sync issue. It works fine now. I'm sure I could use 22050 if I could figure out why exactly it's getting out of sync when using that frequency.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#167593 - elhobbs - Thu Mar 19, 2009 3:13 pm

you tried removing "& 0xFFFE" for the 22050 frequency file and it made no difference? this is effectively removing precision from your frequency calculation. it has a larger effect on 22050 then it does on 32000. So I would expect that 32000 would still have the issue but to a lesser degree.

my suggestion is to separate the frequency of the audio file from the update of the buffers. so if the update function is called 8 times while the current buffer is playing then you just need to load the data during one of those calls. if it drifts a little bit it will not be a problem - so it might update on the 3rd call instead of the 2nd. so in the update function read TIMER0_DATA and see how many samples have played since the last time you loaded data. if some threshold has past and the next buffer still needs to be filled then read more data.

#167618 - headspin - Fri Mar 20, 2009 4:03 pm

I found the answer to my problem thanks to DekuTree

Quote:
Also, using the TIMER_FREQ macro can cause out-of-sync, because CPU timers run at twice the frequency of sound timers. Try using SOUND_FREQ(AUDIO_FREQ) * 2 to guarantee that it matches exactly.

_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game

#167622 - elhobbs - Fri Mar 20, 2009 6:17 pm

excellent! that is a much simpler solution than I thought would be required.