--- In gbadev@y..., "David Clear" <davidc@a...> wrote:
>
> I have an application where I have music source data that is
> converted to DirectSound samples on the fly (i.e. the entire
> tune is too big to be decompressed into one large sample).
In other words, you're working on a music player based
on ADPCM or sub-band audio coding, right? Or is your
"compression" really just a MIDI or MOD player? Both
approaches to music will typically use a double buffer.
> The approach I am considering is a simple double-buffer. Play one
> buffer whilst decoding the next music segment into the other.
That's how most mixers and decompressors work.
> From some tutorials on the web, I have managed to play a single
> buffer, however, I am not sure what to do to flip over to the
> second buffer.
>
> This is what I am doing:
>
> 1. Setup Timer0 at 44.1kHz to play the sample.
I wouldn't suggest 44.1 kHz because it doesn't divide evenly
into the length of a frame.
Previously, I gave the conditions for a good sample buffer size:
* the size should be a factor of 280896 (the time in cycles of
one frame), and
* the size should be a multiple of 4 (the width of the FIFO).
I generally use a pair of 304-sample buffers, which gives a period
of 924 cycles per sample and a sample frequency of 18157 Hz. If I
could spare the CPU time, I could double that to 608 samples and
36314 Hz.
> 2. Setup Timer1 to cascade, counting N samples and then
> interrupting.
Turning on more than one periodic interrupt (vblank and timers)
complicates concurrent real-time programming significantly. To
simplify things, I switch buffers inside my vblank interrupt,
which is why I make my buffers the same length as a frame and
limit my sample rates.
> 3. Set DMA1 from the first soundbuffer.
> 4. Enable Timer0.
>
> Now I'll get an interrupt from Timer1 after N samples.
>
> Q. What do I have to do to seamlessly move over to
> the second buffer?
> - I think I have to:
> 1. Disable Timer0.
> 2. Disable DMA1.
> 3. Reset DMA1 to new source buffer.
> 4. Reenable DMA1.
> 5. Reenable Timer0.
You don't need to do steps 1 and 5, but you do need to wait
a few cycles after step 2 because a write to the DMA channel's
control register needs a couple cycles to take effect.
My mixer code uses the following function to start a buffer going
or to switch buffers.
void dsound_switch_buffers(const signed char *src)
{
DMA[1].control = 0;
/* no-op to let DMA registers catch up */
asm volatile ("eor r0, r0; eor r0, r0" ::: "r0");
DMA[1].src = src; //dma1 source
DMA[1].dst = (void *)0x040000a0; //write to FIFO A address
DMA[1].count = 1;
DMA[1].control = DMA_DSTUNCH | DMA_SRCINC | DMA_REPEAT | DMA_U32 |
DMA_SPECIAL | DMA_ENABLE;
/* DMA_SPECIAL is the special trigger mode for each channel.
For channel 1, it's triggered by the FIFO. If you're using
"standard" headers, it'll probably be called something else. */
}
> This does not work on VisualBoyAdvance (although I have not
> tried on my hardware yet).
The mixer code from Tetanus On Drugs M2 is in 100% commented,
optimized C[1] and works on at least the GBA, VisualBoyAdvance,
Boycott Advance, and BatGBA. Look at that if you're confused
as to how to get a double buffer working.
http://www.pineight.com/gba/
[1] Some consider hand-tweaking C code a bad idea because
different compilers with different switch settings will
compile a given piece of code drastically differently.
However, if your optimized C uses very simple instructions,
the compiler will generate code quite a bit more predictably.
To see how, read ARM application note 34:
http://www.arm.com/support/567GCF/$File/DAI0034A_efficient-c.pdf
--
Damian