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 Environment | Contents | Next: Simple Sound Mixer |