DMANow 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)
REG_DMAxDAD (replace x with the channel number 0 - 3)
REG_DMAxCNT (replace x with the channel number 0 - 3)
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 sampleNow 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. |