gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Coding > GBA sound mixing based on timer (NOT VBlank !)

#177817 - Bregalad - Wed Mar 20, 2013 3:06 pm

I want to create my own sound engine, and I want it to get updated way faster than 60Hz (I plan to do it at aprox. 200Hz).

I found this excellent tutorial, so I'll not be asking how to write a sound engine. However, I'm afraid of something :
Quote:
The easiest kind of buffering for use on GBA is double buffering. With it, you have 2 distinct buffers, each the same size, and use an interrupt to decide when to swap them out. For now, we'll do it with a VBlank interrupt, because it's the safest. Doing it on a timer means opening up possibilities of another interrupt happening first and delaying the swap, causing a click.

The problem is that I really want to do it based on a timer, because VBlank is too slow (and this gives impresision for rapid pitch and voluem changes).
If I update the sound engine at 200Hz, but the sound mixing at 60Hz it will be weird, because all the updates that would be done by the sound engine will not be applied to the mixer until the next VBlank, so only PSG sound channels will be able to benefix from the increased refresh frequency. I want to avoid that if possible.

Is there a way (even if it is a bit tricky or hacky) to make a timer interrupt higher priority than VBlank (i.e. when the timer overflows, it calls the sound engine even if this is during a VBlank interrupt) ?

If no, I still think there is a work arround. I can do a dummy copy of the fist part of the first sound buffer at the end of the second buffer, so that even if the timer interrupt is asserved late, the sound will still continue correctly.
Now the problem is asserting the new address to the sound buffer, since there is jiterring in the interrupt, it should be done by subtracting a constant to the adress, instead simply forcing the start of the first buffer.
Is there a way to do this RMW operation of the sound DMA pointers ?

#177818 - Dwedit - Wed Mar 20, 2013 4:39 pm

When you're synthesizing the sound in software, you have no restrictions on what goes into generating the sound. You are just generating samples for the hardware to play back later. So you can design your mixer to generate sound at any rate your want to, you have no need for aligning things to any particular timing, like 60Hz or 200Hz. As long as you generate enough samples to keep the audio buffer full at the right time, you're good.
The only things that would need precise timing would be PSG sound, since they update immediately unlike samples in a buffer. And if you have a dire need to drive PSG sound at a faster rate, you can also use scanline counting interrupts.

The 60Hz vblank stuff mainly applies to playing back what you have generated, because that provides a decent way to play back sound without using additional timers.
You also need a timer that operates at the sampling rate of the sound, and you need a count-up timer after that to count how many samples have been consumed.
60Hz Vblank stuff is used here so you can regularly count the samples that have been consumed, and trigger more samples to be produced.

If you plan on using the PSG channels, you need to use special trickery to update the volume of the square channels without popping. Use a GBA timer, set the period to match the square channel. When you want to change the volume or pitch of the channel, store the intended new value (and new timer value) somewhere, then enable interrupts for that timer. Have the interrupt handler write the new values to the sound channel and timer. This eats up two timers for the two square channels. I did this in PocketNES.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#177819 - Bregalad - Wed Mar 20, 2013 6:10 pm

Quote:
If you plan on using the PSG channels, you need to use special trickery to update the volume of the square channels without popping. Use a GBA timer, set the period to match the square channel. When you want to change the volume or pitch of the channel, store the intended new value (and new timer value) somewhere, then enable interrupts for that timer. Have the interrupt handler write the new values to the sound channel and timer. This eats up two timers for the two square channels.

This is very clever !
I already knew that "trickery" thing - the sappy engine that 95% of GBA games uses simply updates a new volume at change, and this sounds very ugly if you try to do fades in/outs with MIDI controllers 7. However, most games handled this with ADSR envelopes which are translated to hardware increase/decrease modes.

I was planning to simply use the increase/decrease mode cleverly to handle the volume (apparently this can be done without clicking) but anyways that isn't (yet) my problem.


Quote:
The 60Hz vblank stuff mainly applies to playing back what you have generated, because that provides a decent way to play back sound without using additional timers.

In fact this is exacly the problem. It would be way simpler if I could have the following setup :

* Any timer enabled at ~200Hz, when it overflows it crates an interrupt
* Normal VBlank interrupt only deals with graphics / DMA (does not deal with sound AT ALL)
* The sound engine is run on the timer interrupt, and then the samples for the next tick are mixed to the buffer, then only the interrupt returns.

The problem is that apparently the VBlank has a higher priority which is NOT what I want - I'd like the sound to have a higher priority so I can swap buffers exactly when the timer overflows.

If this is really impossible to get clean sound this way I'll find an alternative but I'd really like to do it this way (and keep graphics and sound separate).
I don't care about timer usage, they probably are not useful to anything other than sound, so all 4 can be used if this is necessary. Using VBlank or HBlank interrupts for sound is really "hacky" IMO (even if the sappy engine typically runs on VBlank).

EDIT :
According to no$ :

Quote:
- User program may freely assign priority to each reason by own logic

Whew ! I'm relived. Now it's all a matter to know how to tell GCC that timer interrupt is more important than VBlank.

#177820 - sverx - Thu Mar 21, 2013 10:34 am

I think GBATek is saying that in your handler you can service the IRQs the order you prefer, not that there's a precedence.
Anyway it also say
Quote:
If user wants to allow nested interrupts, save SPSR_irq, then enable IRQs.
so you might want to allow nested interrupts in your VBlank handler so that your timer interrupts will be serviced immediately.
_________________
libXM7|NDS programming tutorial (Italiano)|Waimanu DS / GBA|A DS Homebrewer's Diary

#177821 - Bregalad - Thu Mar 21, 2013 12:18 pm

So all I'll have to do is enable timer interrupts from within the VBlank interrupt ?
But then, what happens if a timer interrupts occurs during a DMA transfer ? I suppose it will only be asserted at the end of the DMA transfer, and this delay in the assertion of the interrupt can be problematic to swap the buffers at the right time.

This can be solved by splitting large DMA transfers into multiple smaller DMA transfers, or do all the transfers by soft.