Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Introduction
 Direct Sound
 DMA
 Putting it all
 together


 Source code
 Printable version
 Discuss this article

DMA

Now that we understand how Direct Sound uses the data in the FIFO, all we have to worry about is keeping it filled with fresh samples. On the surface, this might appear to be problematic. Obviously a real game isn't going to want to stop every 761 CPU cycles and make sure the FIFO isn't empty. Wouldn't it be nice if we could just tell the FIFO to refill itself whenever its supply of fresh samples is running low? Well, luckily for us, the GBA has just such a solution in the form of the DMA (Direct Memory Access) channels 1-2. For those of you that are unfamiliar with the concept, the DMA channels are responsible for shuttling data around the system without the aid of the CPU (note that their usefulness also extends far beyond the realm of audio playback). Their operation is very simple, and is controlled by three memory-mapped registers, which are outlined below.

REG_DMAxSAD (replace x with the channel number 0 - 3)
Bits Function
0-31 Source address of the data being sent

REG_DMAxDAD (replace x with the channel number 0 - 3)
Bits Function
0-31 Destination address of the data being sent

REG_DMAxCNT (replace x with the channel number 0 - 3)
Bits Function
0-15 The word count of the data being sent
16-20 Unused
21-22 Destination register update
00 = increment the destination counter after each word transfer
01 = decrement the destination counter after each word transfer
10 = destination is always the Direct Sound FIFO
11 = increment the destination register after each word transfer and reset on DMA completion (used to create so-called Mode 7 effects)
23-24 Source register update
00 = increment the source counter after each word transfer
01 = decrement the source counter after each word transfer
10 = fixed (the source counter is the same for each word transfer)
11 = invalid setting
25 DMA repeat (causes the DMA to be repeated on the interval specified by the timing bits below)
0 = No
1 = Yes
26 Transfer word size (0 = 2 byte words, 1 = 4 byte words)
27 Unused
28-29 Timing control (determines when the DMA begins)
00 = DMA starts immediately
01 = DMA starts at the next vblank
10 = DMA starts at the next hblank
11 = DMA starts when the FIFO needs re-filling
30 Causes an interrupt upon completion of the data transfer (0 = No, 1 = Yes)
31 Enable the transfer (0 = transfer disabled, 1 = transfer enabled)

To initiate a DMA transfer we first set the source and destination registers of the data we are sending, and then set the appropriate bits in the control register to enable the transfer.

Using the above register definitions, the steps for setting up a DMA to the FIFOs are fairly straightforward. The code below will initiate a DMA transfer to FIFO A using Timer 0.

// DMA channel 1 register definitions
#define REG_DMA1SAD         *(u32*)0x40000BC  // source address
#define REG_DMA1DAD         *(u32*)0x40000C0  // destination address
#define REG_DMA1CNT         *(u32*)0x40000C4  // control register

// DMA flags
#define WORD_DMA            0x04000000
#define HALF_WORD_DMA       0x00000000
#define ENABLE_DMA          0x80000000
#define START_ON_FIFO_EMPTY 0x30000000
#define DMA_REPEAT          0x02000000
#define DEST_REG_SAME       0x00400000

// Timer 0 register definitions
#define REG_TM0D            *(u16*)0x4000100
#define REG_TM0CNT          *(u16*)0x4000102

// Timer flags
#define TIMER_ENABLED       0x0080

// FIFO address defines
#define REG_FIFO_A          0x040000A0
#define REG_FIFO_B          0x040000A4

// our Timer interval that we calculated earlier (note that this
// value depends on our playback frequency and is therefore not set in
// stone)
#define TIMER_INTERVAL      (0xFFFF - 761)

// set the timer to overflow at the appropriate frequency and start it
REG_TM0D   = TIMER_INTERVAL;
REG_TM0CNT = TIMER_ENABLED;

// start the DMA transfer (assume that pSample is a (signed char*)
// pointer to the buffer containing our sound data)
REG_DMA1SAD = (u32)(pSample);
REG_DMA1DAD = (u32)REG_FIFO_A;
REG_DMA1CNT = ENABLE_DMA | START_ON_FIFO_EMPTY | WORD_DMA | DMA_REPEAT;

Note that DMA channels 1 and 2 appear to be hardwired to feed FIFOs A and B respectively. This means that you must use DMA channel 1 to send sound data to FIFO A and DMA channel 2 send sound data to FIFO B.

Stopping a sample

Now that we've got the sound playing, it would be helpful if we knew how to shut it off. This turns out to be a little more difficult than it sounds. You see, the DMA controller doesn't care about the word count setting (even if you include it in the control register) on a DMA to the FIFOs. Unless you manually disable the transfer by setting the enable bit (31) to zero upon the sample's completion, it will continue to feed the FIFOs whatever data happens to follow your sample in memory (other variables, code, garbage, etc.). Clearly, this isn't a good thing.

There are a couple of different ways to determine when a sample has finished. One of the more popular methods involves using another Timer to keep track of the number of samples played, and having it generate an interrupt upon its overflow, which was cleverly pre-calculated to occur right as the sample is finishing. However, I don't want to get into interrupt handlers for this demo (we're trying to keep it simple, after all). A more facile solution (and the one we're going to use) is to count the vblanks. You see, like a television, the GBA updates its screen approximately 60 times per second (59.727 to be exact). If we know the length of a sample, we can easily calculate the number of screen refreshes that will occur during its playback. Then all we have to do is count the vblanks (a much simpler task than setting up an interrupt handler) and stop the sample accordingly. I'm not going to cover this method in too much detail because it's not directly related to sound playback. However, it will be implemented in the sample program, so feel free to take a look at it.





Next : Putting it all together