Basic Sound Implementation

For now, we'll just worry about getting the sound to properly loop around a ring buffer we create.

Create a new file, I'll call mine "mod.s". Add it to your source directory.

What we need here is some functions to setup the sound registers and start the sound DMA. We also need to setup an interrupt routine to reset the DMA registers when the sound reaches the end. Here's my code:

/****************************************************************
 * Small GBA MOD Player
 ****************************************************************/

/***************************************************
 * global symbols
 ***************************************************/

.global MOD_Setup

.global	MOD_WaveBufferL
.global MOD_WaveBufferR

/***************************************************
 * definitions
 ***************************************************/

.equ	INT_TIMER1,	(1<<4)

.equ	DMA1SAD,	0x40000BC
.equ	DMA1DAD,	0x40000C0
.equ	DMA1CNT,	0x40000C4
.equ	DMA2SAD,	0x40000C8
.equ	DMA2DAD,	0x40000CC
.equ	DMA2CNT,	0x40000D0

.equ	FIFO_A,		0x40000A0
.equ	FIFO_B,		0x40000A4

.equ	TM0CNT,		0x4000100
.equ	TM1CNT,		0x4000104

.equ	SOUNDCNT_H,	0x4000082
.equ	SOUNDCNT_X,	0x4000084

.equ	MIXING_RATE,	16384	@ 16KHz output
.equ	MIXING_TIMER,	1024	@ (timer counter)
.equ	BUFFER_SIZE,	128	@ (length of wave buffers)

@----------------------------------------------------------
.bss
.align
@----------------------------------------------------------

/**********************************************************
 * MOD_WaveBufferL
 * MOD_WaveBufferR
 *
 * Contains wave data for the left output.
 * (wow there goes half of our memory)
 **********************************************************/
MOD_WaveBufferL: .space	BUFFER_SIZE
MOD_WaveBufferR: .space	BUFFER_SIZE

/**********************************************************
 * bufferSlice
 *
 * Selects which half of the wave buffers to output to.
 **********************************************************/
bufferSlice: .space 1

.text
.thumb
.align 2

/**********************************************************
 * MOD_Setup()
 *
 * Initialize System
 **********************************************************/
.thumb_func
MOD_Setup:
	push	{lr}
	bl	MOD_InitSound		@ initialize sound
	pop	{r0}
	bx	r0			@ return arm/thumb

/**********************************************************
 * MOD_ResetDMA()
 *
 * Reset Sound DMA Transfers.
 **********************************************************/
.thumb_func
MOD_ResetDMA:
	
	ldr	r0,=DMA1SAD
	mov	r1, #0
	str	r1, [r0, #8]		@ disable DMA1
	str	r1, [r0, #8+12]		@ disable DMA2
	
	ldr	r1,=MOD_WaveBufferL	@ set source addresses
	str	r1, [r0, #0]
	add	r1, #BUFFER_SIZE
	str	r1, [r0, #0+12]
	
	ldr	r1,=FIFO_A		@ set destination address
	str	r1, [r0, #4]
	add	r1, #4
	str	r1, [r0, #4+12]

	ldr	r1,=0xB6000004		@ enable+mode3+32bit+repeat+inc/inc+4 words
	str	r1, [r0, #8]			
	nop				@ when starting dma
	nop				@ delay 2 clocks before accessing other dma registers
	str	r1, [r0, #8+12]
	bx	lr
	

/**********************************************************
 * MOD_InitSound()
 *
 * Initialize the sound system and start the DMA process.
 **********************************************************/
.thumb_func
MOD_InitSound:

	push	{lr}
	
	mov	r0, #INT_TIMER1			@ Install our DMA-Reset routine with libgba irq handler
	ldr	r1,=MOD_Routine		
	bl	irqSet				@ irqSet( INT_TIMER1, MOD_Routine );
	
	mov	r0, #INT_TIMER1
	bl	irqEnable			@ irqEnable( INT_TIMER1 );
	
	bl	MOD_ResetDMA

	ldr	r0,=SOUNDCNT_H
	mov	r1, #0x80
	strh	r1, [r0, #2]			@ master sound enable
	ldr	r1,=0b1001101000001100		@ reset fifos, use timer0, A=left, B=right, 100% volume
	strh	r1, [r0]

	ldr	r1,=MOD_WaveBufferL		@ clear wave buffer
	mov	r2, #BUFFER_SIZE/4*2
	mov	r0, #0
1:	stmia	r1!, {r0}
	sub	r2, #1
	bne	1b
	
	ldr	r0,=TM0CNT
	ldr	r1,=0x00C40000 + ((-(BUFFER_SIZE/2))&0xFFFF)
	str	r1, [r0, #4]			@ start timer1 as sample counter, enable interrupt
	ldr	r1,=0x0080FC00
	str	r1, [r0]			@ start timer0 as sampling-rate
						@ timer1 will overflow after half of the mixing buffer is played
	pop	{pc}				@ return thumb

/*****************************************************************
 * MOD_Routine
 *
 * Work-routine. Called every time half of the wave buffer
 * gets processed.
 *****************************************************************/
.thumb_func
MOD_Routine:

	push	{r4-r7}				@ preserve registers
	push	{lr}
	ldr	r4,=bufferSlice			@ if slice == 1 then reset DMA!
	ldrb	r5, [r4]
	cmp	r5, #0
	beq	1f
	bl	MOD_ResetDMA
1:
	mvn	r0, r5				@ toggle slice
	strb	r0, [r4]
	
	pop	{r3-r7}				@ return arm/thumb
	bx	r3
	

Such a lot of code for something so simple. I'll explain the parts of it.

First we have MOD_Setup(), this is a function visible outside of the lib (see ".global" at the top). This function just sets up the audio system.

Inside MOD_InitSound does a bunch of things: sets up TIMER1's interrupt to call MOD_Routine, initializes DMA, sets Master sound enable, initializes the Direct Sound registers, clears the wave buffers, starts the sample-counting timer, and finally, starts the sampling timer.

The low hword of the TMxCNT register is the initial counter value and the reload value. We use FC00 (-1024) to indicate 1024 clock cycles per 'tick'. The clock speed of the GBA is 16.777216 MHz (2^24), so using 1024 clocks per tick gives us a 16KHz sampling rate ((2^24 / 1024) = 16384).

We are also using TIMER1, TIMER1 will be set in 'count-up' mode. This means that it is not feed by the CPU clock. Instead, it is feed by the overflowing of TIMER0. This gives us an accurate sample counter to time when to reset DMA and/or mix more samples.

The MOD_ResetDMA function is simple, it just writes 0 to the control registers of DMA1 and 2, sets the source/dest addresses, and then enables them again. We don't need to set the source/dest addresses after the one time during initialization, but we save a few instructions if we leave it here.

To test that these functions work, I put a bit of code in main.c to fill the wavebuffers up with some data and tested if the playback had any fuzz (it didn't).

Previous: Testing EnvironmentContentsNext: Simple Sound Mixer