GP32 Audio


Drag

Member
Joined
Jan 11, 2004
Messages
371
Age
36
Location
USA
Website
drag.wootest.net
I've created a software synthesizer. So obviously, the synthesizer will stream its output to a buffer which is outputted (as opposed to playing wav files).

Now, I use SDL to get audio because that way I won't have to fool around with directsound, and besides, I intend to use this in my GP32 games.

My problem is that I have no idea what to do. SDL calls my function automatically whenever it needs to fill a buffer up. SDL will pass the amount of bytes I need to fill, and a pointer to the buffer that I need to fill. The function calls the synthesizer's render function, then writes the output to the buffer, increases the pointer, and then loops until all of the requested bytes are filled.

So I guess what I need is a ringbuffer, and an IRQ to tell me when I need to write to the ringbuffer. I use Mr.Mirko's SDK, so is there something in there that I can use?
 
Have a look at the BOR source code, as I implemented the sound there in exactly the way you have just described.

Actually, the OpenBOR src is probably easier to find, as I heard it's made it to sourceforge, whereas mine is burried in a huge thread somewhere.
 
you can have a try with mr.spiv's mlib
Another World was implemented in SDL, all I did was to register the callback used for SDL with the init fct of mr.spiv, and bang it worked (well not really, but was on a good way)... just check that the buffers size still match.
ask mr.spiv on #gp32dev i'm sure he'll be glad to help you
 
Which would happen to be the reason I decided to not use it. :)

Not to mention it looks like the GP32 sdl might be slower than my program needs to be.

Squidge: I appreciate your help, but I cannot locate either of those source codes. :(

I'm sure there's a way to get a ringbuffer set up with Mr.Mirko's SDK, but the documentation doesn't shed any obvious light. I just need a ringbuffer set up, an IRQ to tell me when the buffer is ready to be written to, how many bytes need to be written to the buffer, a pointer to the buffer I need to write to, and I'd be set.
 
Whoops! Ok, I don't know how to use Sourceforge then, I just now found where it puts the source. Thanks Squidge. :S

I'll look at it, if I still have questions, I'll ask here.
 
Hey guys,

I used a ringbuffer when I ported libmikmod, but I had to alter the code (Mr.Mirko SDK) to get it to work, because it wouldn't use samplebuffer1. I had to change it to use samplebuffer2, and it worked fine.

There are a couple of snippets on my site (as gp32rich says), namely:

snippet: Hitmen - Ring buffer for raw PCM samples
snippet: CHN - Ring buffer using official SDK
But these don't use Mr.Mirko SDK.

The source that plays the ringbuffer in my mikmod port is (gp32_drv.c) - Just ignore the VC_ parts that are part of mikmod.

Code:
#define GP32_buffersize 2048
#define GP32_ringsize GP32_buffersize*2

static unsigned char *GP32_buffer=NULL;

static BOOL GP32_IsThere(void){
	return 1;
}

static BOOL GP32_Init(void){
	md_mode = DMODE_STEREO | DMODE_16BITS | DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX;
	md_mixfreq = 44100;
	gp_initSound( 44100, 16, GP32_ringsize ); // 44k sound, 16bpp, 2x4k buffers
	GP32_buffer = malloc( GP32_buffersize ); // Half of the 8k ringbuffer
	gp_clearRingbuffer();
	return VC_Init();
}

void GP32_Stop(void){
	gp_clearRingbuffer();
}

void GP32_Restart(void){
	// No restart yet
}

static void GP32_Exit(void){
	gp_clearRingbuffer();
	free( GP32_buffer );
	VC_Exit();
}

static void GP32_Update(void){
	unsigned long bytesread;
	
#ifdef GP32_DEBUG
	//gp_debug( 1, "Read bytes" );
#endif
	
	bytesread = VC_WriteBytes(GP32_buffer, GP32_buffersize);	
	
#ifdef GP32_DEBUG
	//gp_debug( 1, "Read %d bytes", bytesread );
#endif
  
	if(!bytesread) return; // exit if a whole buffer hasn't been read
  
	gp_addRingsegment( (unsigned short*)GP32_buffer );
}

static void GP32_PlayStop(void){
	gp_clearRingbuffer();
	VC_PlayStop();
}

EDIT:
Basically, just set up the ringbuffer:
gp_initSound( 44100, 16, GP32_ringsize ); // 44k sound, 16bpp, 2x4k buffers

Then whack your raw data into it:
gp_addRingsegment( (unsigned short*)GP32_buffer );
Be wary - it will wait on the above line until half the buffer is available, and then populate the half it isn't using. I never got around to altering Mr.Mirkos code to simply return FALSE if there wasn't enough room (which would be better).

And when you are done:
gp_clearRingbuffer();

Aparently (with talks with Mr.Mirko) to stop the buffer you just clear it. There is no function to actually stop the timers etc, which IMO would be cleaner.

EDIT 2:
See also:
http://www.gp32x.de/board/index.php?showt...ndpost&p=216848
http://www.gp32x.de/board/index.php?showt...ndpost&p=213297
 
Last edited by a moderator:
Thanks for the info, Pea! :)

I remember that whole mikmod ordeal, and the waiting problem. I probably am going to need it to not pause on that line like that. :(

However, I've been looking at Squidge's BOR audio code there, and at the same time looking at that code snippet for ringbuffers and PCM stuff, and I think I'm getting a good idea on how to START getting this to work.

It's too bad geepee32 doesn't have sound support, I'll have to put this on the real hardware each time I try it out.
 
Getting it to not pause is easy. I'll do it for you now if you like....

Change the following OLD function (src/sound/init1330.c):
Code:
void gp_addRingsegment( u16 *add_buffer ) {
     int i;
     u16 *SEGMENT0 = (u16*)SAMPLEBUFFER0;
     u16 *SEGMENT1 = ((u16*)SAMPLEBUFFER0) + SEGMENTSIZE/2;   // ((short*)0xf000)+4 is exactly the same as ((short*)0xf000)[4]
     // detect playing segment
     if ( gp_getSamplepos() > (u32)SEGMENT1 ) {                         // we are in upper segment
       for (i=0;i<SEGMENTSIZE/2;i++) SEGMENT0[i] = add_buffer[i];  // fill lower segment
       while (1) {  if (gp_getSamplepos() < (u32)SEGMENT1) break;  }    // wait reaches lower segment
     }
     else {
        for (i=0;i<SEGMENTSIZE/2;i++) SEGMENT1[i] = add_buffer[i]; // we are in lower segment, fill upper segment
        while (1) {  if (gp_getSamplepos() > (u32)SEGMENT1) break;  }   // wait reaches upper segment
     }
}

To this NEW version (untested):
Code:
static char _gp_segment = 0;
char gp_addRingsegment( u16 *add_buffer ) {
	int i;
	u16 *SEGMENT0 = (u16*)SAMPLEBUFFER0;
	u16 *SEGMENT1 = ((u16*)SAMPLEBUFFER0) + SEGMENTSIZE/2;   // ((short*)0xf000)+4 is exactly the same as ((short*)0xf000)[4]
	// detect playing segment
	if ( gp_getSamplepos() > (u32)SEGMENT1 ){                         // we are in upper segment
  if (_gp_segment==1){ return 0; }
  for (i=0;i<SEGMENTSIZE/2;i++) SEGMENT0[i] = add_buffer[i];  // fill lower segment
  _gp_segment = 1;
	}
	else {
  if (_gp_segment==0){ return 0; }
  for (i=0;i<SEGMENTSIZE/2;i++) SEGMENT1[i] = add_buffer[i]; // we are in lower segment, fill upper segment
  _gp_segment = 0;
	}
	return 1;
}

The OLD code - you just fire your raw data at it and it fills half the buffer, and then WAITS until the pointer is into the next buffer before exiting the function.

The NEW code will exit immediately if the pointer hasn't progressed to the next buffer yet, so every time you write to the buffer you MUST check the return value before you step through your raw data.

Basically:

Init sound
Init pointer to start of my raw data

LOOP
Attempt to fill buffer
Did buffer accept my data?
Yes - Advance pointer within data
/LOOP

Clear sound

Hope it helps!



EDIT:
On another note, pulling the following two lines out of the function (and placing them as globals in the init function for example) will give you a very tiny speed boost, as this does not need to be done every time the function is called:
Code:
u16 *SEGMENT0 = (u16*)SAMPLEBUFFER0;
u16 *SEGMENT1 = ((u16*)SAMPLEBUFFER0) + SEGMENTSIZE/2;
 
Thanks, Pea! You're always helping me! :)

Now, I'm also a little concerned about crackly sound. Mainly, if I don't write to the portion of the ram I'm using for sound accidently, I'll be fine, but I also need to set up some memory modes or something. I'm sure I can find what I need to know in that DMA bug thread that's in here, but I think that thread is a little aged. Useful, but aged. Are there any breakthroughs that I should know about that possibly aren't in that thread?
 
Sorry, I don't know much about the sound itself and its implementation. I do know that the buffers used by the ring buffer code are flagged to not cache:

Code:
gp_setMMU( SAMPLEBUFFER0, (SAMPLEBUFFER0+16384)-1, 0xFFA );  //no cache for samplebuffer
gp_setMMU( UNCACHED4KB0 , (UNCACHED4KB0 +16384)-1, 0xFFA );  //no cache for extra_ram

Is this what you are after? According to Mr.Mirkos docs (the last sentence is interesting :) ):

void gp_setMMU(u32 mempos_start,u32 mempos_end,int mode)
--------------------------------------------------------------------------------
CPU/Memory control ( MMU setting )

u32 mempos_start start address of the memory region
you want the MMU changes to affect
u32 mempos_end end address of the memory region
you want the MMU changes to affect
u32 flags This value is ORed to the second level MMU page table descriptor
Only the low 12 bits are used
0xFF2 - to disable cache and writeback
0xFFA - to enable cache and disable writeback
0xFFE - to enable cache and writeback
RETURNS nothing

INFO:
This function allows you to Set the Memory mode for a region of memory

NOTE:
Keep in mind that the granularity of MMU pages is 4KB, which means
that the addresses you feed into the BIOS call must 4KB aligned addresses.

If you are using sound, the framebuffer must be (sometimes) in 0xFF2 mode
 
Back
Top