Direct SoundThe GBA has two digital-to-analog converters (dubbed Direct Sound A and Direct Sound B) for playing back signed 8-bit PCM samples. Functionally these two channels are identical and can be used at the same time, but you really only need to use one. To get the GBA to play a sound, all you have to do is feed these channels the correct data at the correct rate and the hardware will take care of the rest. Sounds simple enough, right? I assume there are two Direct Sound channels to facilitate stereo sound in games that need it. However, since the GBA only has a single monaural speaker on the right side of its casing, the only way you're going to hear stereo sound is if you use the headphone jack. The FIFOWell, as is usually the case, it's a little more complicated than that. It turns out you can't just send sample data directly to the playback hardware. Instead, Direct Sound decides according to its own internal criteria (which we'll cover shortly) when it needs data. It then reads a single sample (one byte) out of a queue called the FIFO (First In First Out), and sends it along to the sound chip. There are actually two FIFOs; one for Direct Sound A and the other for Direct Sound B. If I lost anyone there, don't worry about it. After the initial setup, all we have to be concerned with from a programmer perspective is keeping the FIFO(s) full. REG_SOUNDCNT_HThe Direct Sound channels are controlled by REG_SOUND_CNT_H (address 0x04000082). A layout of the bits in this register can be seen below. Everything should look familiar except for the Timer designation, which we will cover below.
TimersSo why can't you just feed your sample data to Direct Sound all at once? Well, let's consider an example. Say you have an audio clip recorded at a sampling rate of 22050 Hz. This means that when we play the clip back, we want to process 22050 samples per second and no more. The GBA's CPU runs at 16.7 MHz (or 2 ^ 24 Hz) so a little math tells us that we only need to send a sample once every 761 CPU cycles.
As you can see, there needs to be a fixed rate at which we send samples to the playback hardware. This is where the Timers come in. The GBA has four hardware Timers that count up to 0xFFFF (65535) and can be set to increment after every 1, 64, 256, or 1024 CPU cycles. Since the Timer values are 16-bit, when a Timer with a count of 0xFFFF is incremented it is said to overflow back to whatever value it was originally set with (ie. if you set a Timer to count up from 0xBBBB, when it overflows it will reset to 0xBBBB). For our purposes, we can use the overflow of Timers 0 and 1 to tell Direct Sound that it is time to send a sample to the playback hardware. You can see in the above definition of REG_SOUNDCNT_H that bits A and E are used to specify a Timer. When you set these bits, you're basically telling Direct Sound to watch one of these Timers, and to send a one-byte sample to the playback hardware when it overflows. Before we go any further, here is the layout of the Timer control registers. REG_TMxCNT (replace x with the Timer number 0 - 3)
REG_TMxD (replace x with the Timer number 0 - 3)
So to start a Timer, we set the initial value that we want to count up from in REG_TMxD, and then set the appropriate bits in REG_TMxCNT (for us, this will only involve setting bit 7 to enable the Timer). Now, back to our example. We have established that we want to send a one-byte sample from the FIFO to the playback hardware once every 761 CPU cycles, and we know that the sending of a sample is triggered by the overflow of the Timer(s) specified in REG_SOUNDCNT_H. Now, because we want the specified Timer to overflow every 761 CPU cycles, we will set its initial value to 65536 (or 0xFFFF) - 761. My calculator comes up with 64774 (or 0xFD06). Now that we've gotten all that down, we have all the information we need to initialize Direct Sound. As before, we're going to add a couple of #defines to improve readability. This will allow you to logically OR a series of bit-flags together to simplify setting the register. You'll notice here that I refer to register REG_SOUNDCNT_L, which controls the sound channels 1-4 (among other things). We won't be using them in this series, but since they are part of the sound system, it's a good idea to zero the register out. For those that haven't seen the | operator before, it represents for a logical OR, and it can be used to easily combine flags when setting a register. For example, if you wanted to set just the first and last bits of a 16-bit number, you would assign it the value of 0x0001 | 0x8000, which evaluates to 0x8001. // register definitions #define REG_SOUNDCNT_L *(u16*)0x04000080 #define REG_SOUNDCNT_H *(u16*)0x04000082 // flags #define SND_ENABLED 0x00000080 #define SND_OUTPUT_RATIO_25 0x0000 #define SND_OUTPUT_RATIO_50 0x0001 #define SND_OUTPUT_RATIO_100 0x0002 #define DSA_OUTPUT_RATIO_50 0x0000 #define DSA_OUTPUT_RATIO_100 0x0004 #define DSA_OUTPUT_TO_RIGHT 0x0100 #define DSA_OUTPUT_TO_LEFT 0x0200 #define DSA_OUTPUT_TO_BOTH 0x0300 #define DSA_TIMER0 0x0000 #define DSA_TIMER1 0x0400 #define DSA_FIFO_RESET 0x0800 #define DSB_OUTPUT_RATIO_50 0x0000 #define DSB_OUTPUT_RATIO_100 0x0008 #define DSB_OUTPUT_TO_RIGHT 0x1000 #define DSB_OUTPUT_TO_LEFT 0x2000 #define DSB_OUTPUT_TO_BOTH 0x3000 #define DSB_TIMER0 0x0000 #define DSB_TIMER1 0x4000 #define DSB_FIFO_RESET 0x8000 // we don’t want to mess with sound channels 1-4 REG_SOUNDCNT_L = 0; // enable and reset Direct Sound channel A, at full volume, using // Timer 0 to determine frequency REG_SOUNDCNT_H = SND_OUTPUT_RATIO_100 | DSA_OUTPUT_RATIO_100 | DSA_OUTPUT_TO_BOTH | DSA_TIMER0 | DSA_FIFO_RESET; |