Gameboy Advance Programming

Overview

When I first heard about the GBA I was quite excited as it would be an oppurtunity to practice some lower level coding on a platform very similar to the SNES. This was basically a childhood dream, so I tried to learn as much as possible about it and ended up releasing a demo that landed me a job developing games for it, which was a lot of fun, but also a lot of work :).

This page has a basic overview of the techniques used in the two main features of the demo, the mode7 style track and the xm playback engine.


The game in action

You can download the game here. It includes a basic map editor which you can use to fool around with the example track used in the game. Unfortunately I will not be releasing the source for the game at this time.

Tile editing suite

Description pending... Click on the image to see a larger screenshot.

Mode7 Engine

Who doesn't remember the false perspective track used to great effect in f-zero and mariokart? I basically aimed to recreate this type of game surface, which turned out to be a bit harder that originally anticipated. Essentially at every horizontal line on the screen you modify the values that the GBA uses to step through the tilemap (essentially a big 1024x1024 picture made of 8x8 blocks) to create the illusion of a plane. Standard linear algebra with a bit of calc is all that is neccesary, then set up a repeating h-blank dma (which is basically custom made for this type of effect) and you are set to go.

When the demo was released it was the first non-commercial product to demonstrate hardware mode7 on the gba (I believe, please feel to let me know if I'm incorrect). It also used mode switching to show the parallax background scrolling around on the top.

XM Playback

This was something that I was quite proud of back when it was implemented. Looking back on it now there are quite a few ways that I could have done things much better, but it was my first real experiment with sound playback and mixing so that's forgivable. The basic concept is that you have a group of channels which can have instruments being played on them. These instruments are simply digital samples representing the waveform of the sound. Then to play the music back you apply some simple effects to the samples and mix them all together.

The key part is keeping CPU usage as low as possible, so the inner mixing loops were coded in ARM assembler. Here is the main mixer routine, perhaps you can get some use out of it (or not ;).

C definition

typedef struct _sGxmChannel
{
	u32 lowSpeed;
	u32 count;
	s8 *curSample;
	s8 *loopEnd;
	s8 *loopStart;
	u16 highSpeed;
	u16 pad;
	s8 volume;
	u8 active;
	u8 flags;	// ping-pong looping
	u8 curPingPong;
};

void MixInLoop(u32 numBytes,sGxmChannel *channel,s16 *buffer);

ARM code

/* r0 is number of bytes to mix
   r1 is pointer to the channel
   r2 is the address of the mix buffer

	sGxmChannel is as follows -   
0	u32 lowSpeed;
4	u32 count;
8	s8 *curSample;
12	s8 *loopEnd;
16	s8 *loopStart;
20	u16 highSpeed;
22	u16 pad;
24	s8 volume;
25	u8 active;
26	u8 flags;	// ping-pong looping
27	u8 curPingPong;
*/
MixInLoopFunc:
	stmdb sp!,{r0-r12,lr}

	stmdb sp!,{r1}
	mov r8, r2		@ address of mix buffer
	mov r2, r0		@ number of bytes to mix
	mov r0, r1		@ base of structure
	mov r1, #24		@ offset
	ldrsb r3,[r0,r1]	@ volume
	mov r1, #0
	ldr r4,[r0,r1]	@ low speed
	mov r1, #20
	ldrh r5,[r0,r1]	@ high speed
	mov r1, #4
	ldr r6,[r0,r1]	@ count
	mov r1, #8
	ldr r7,[r0,r1]	@ cur sample position
	mov r1, #12
	ldr r9,[r0,r1]	@ loop end
	mov r10,#1		@ sample active

	mov r1, #26
	ldrb r12,[r0,r1] @ flags
	cmp r12,#0
	beq MixLoopbackNoLoop
	cmp r12,#1
	beq MixLooping
	b MixPingpong

/* r0 and r1 and temporaries
   r2 = number of bytes to mix
   r3 = volume of sample
   r4 = low speed
   r5 = high speed
   r6 = count
   r7 = pointer to 8 bit signed sample
   r8 = mix buffer address
   r9 = loop end
   r10 = sample active*/
MixLoopbackNoLoop:
	ldrsb	r0,[r7]
	adds	r6,r6,r4
	adc		r7,r7,r5
	cmp		r7,r9
	bge		MixNoLoopOff	@ break out of loop
	ldrsh	r1,[r8]
	mla		r1,r3,r0,r1
	strh	r1,[r8],#2
	subs	r2,r2,#1
	bne		MixLoopbackNoLoop
	b MixDone
MixNoLoopOff:
	mov		r6,#0
	mov		r10,#0		@ set sample to inactive
	b MixDone
	
/* r11 = loop length */
MixLooping:
	mov r1,#16
	ldr r11,[r0,r1]		@ loop start
	sub r11,r9,r11
MixLoopbackLooping:
	ldrsb	r0,[r7]
	adds	r6,r6,r4
	adc		r7,r7,r5
	cmp		r7,r9
	subge	r7,r7,r11	@ loopback to beginning
	ldrsh	r1,[r8]
	mla		r1,r3,r0,r1
	strh	r1,[r8],#2
	subs	r2,r2,#1
	bne		MixLoopbackLooping
	b MixDone

MixPingpong:
	b MixDone

MixDone:
	/* Update the structure */
	ldmia	sp!,{r1}
	mov		r0, r1		@ base of structure
	mov		r1, #4		@ offset
	str		r6, [r0,r1]	@ count
	mov		r1, #8
	str		r7, [r0,r1] @ current sample pos
	mov		r1, #25
	strb	r10, [r0,r1] @ sample active or not

	/* Return */		
	ldmia	sp!,{r0-r12,lr}
	bx lr

.GLOBAL		MixInLoop    

MixInLoop:
	stmdb	sp!,{r7-r8,lr}
	ldr r7,=MixInLoopFunc
	mov lr,pc
	add lr,lr,#4
	bx r7
	ldmia	sp!,{r7-r8,lr}
	bx lr

Back to Projects page

Back to projects.