June 14th, 2011, 02:57 Posted By: wraggster
News via http://dsx86.patrickaalto.com/DSblog.html
For the past week I have been working on the new audio system for DS2x86. As I mentioned in the previous blog post, the code was mostly done by last Sunday, but the problem was that it sounded very bad and also seemed to cause the games to hang much more frequently than with the old audio code. The current status is that the new code is in use, and works at least as well (if not better) in the games that had audio also with the old code. The new code also has ADPCM audio supported, and it works fine in Warcraft, when the original code either failed to initialize or caused the game to crash very often.
I have been using four different games to test the new audio code, as each of these games use somewhat different SoundBlaster audio features:
Supaplex uses the plain simple DMA transfers, playing one digitized sample at a time. The samples have varying sample rates.
Doom uses auto-initialize DMA method, with a 1024-sample buffer but with an IRQ request after every 128 samples. The playing rate is 11kHz.
Warcraft uses auto-initialize DMA with 2048-sample buffer and IRQ request every 1024 samples, with 11kHz sample rate.
Duke Nukem 2 uses various ADPCM sample formats with simple DMA transfers, and with varying sample rates.
I am using 22050Hz audio playing rate in DS2x86. In the original audio code I had initialized the DSTwo audio transfers to 3*128 samples per transfer (with the ds2io_initb() call), as that was the closest I could get to be able to send a new audio buffer within my 60Hz main emulation timer handler. That number was derived from the fact that I would need to send 367.5 (= 22050/60) new samples during each 60Hz timer interval, and the need of the transfer size to be divisible by 128. Using the 60Hz timer for this also meant that I could not emulate an SB IRQ faster than at 60Hz (which again corresponds to 367.5 samples at 22050Hz). For example Doom wanted to get an SB IRQ after every 128 samples have been played. As Doom played the audio at 11kHz (which is very close to half the 22050Hz playing rate I use), I should have generated an SB IRQ after every 256 output samples, or at 85Hz (= 11000/128). To make the audio in Doom work, I had coded a special hack into my audio code where I slowed down the sample rate if a game wants SB IRQs faster than my code could provide them. Obviously this was not a proper solution, so in my new audio code I wanted to handle this situation better.
The basic idea of my new audio code is that the emulation interrupt runs at 4*60Hz (240Hz) and calls the screen and keyboard handling only during every fouth interrupt. The audio emulation is handled at 240Hz, which should be fast enough for any SB IRQ frequency a game would need. By last Sunday I had implemented this new audio code, using 128 samples per transfer in ds2io_initb(), with a separate 1024-sample ring buffer that is filled in the 240Hz interrupt. Since 22050/240 = 91.875, I had designed the ring buffer filling algorithm so that it tried to fill the buffer with approximately 96 samples in each 240Hz interrupt, with the actual amount adjusted by the number of DSTwo IO/ layer audio buffers in use. Since the DSTwo I/O layer has 4 audio buffers, I calculated the needed new samples as 128-ds2_checkAudiobuff()*32. And in every interrupt where the ring buffer had >=128 samples, I sent the buffer to the DSTwo I/O layer.
I thought this code was much better than the original one, however there were several problems that took me pretty much the whole of last week to fix:
Doom had a weird looping/stuttering problem, where parts of the audio kept repeating.
The audio had a horrible constant warble, regardless of what audio was playing.
All games (playing audio) kept hanging within a minute or two of playing them.
I first debugged the behaviour in Doom, as that was one of the main problematic games I tried to fix with the new code. At last I found that the problem was in how the timer and SB IRQs interract in the Doom code. For some peculiar reason, Doom does not fill the 1024-sample DMA buffer during the SB IRQ, but instead in the timer IRQ. And with my new 240Hz maximum SB IRQ frequency, when the DSTwo audio buffers were empty, Doom could get two SB IRQs with no timer IRQ in between, and thus it skipped one 128-sample block in the DMA buffer, which in turn caused that block to play some old data that was in the buffer. I fixed this problem by fine-tuning the amount by which I fill the ring buffer, and never continuing to fill it after it was time to send the SB IRQ to the emulated game.
The second problem with the warbling sound seemed to be simply caused by the 128-sample transfer size. The DSTwo I/O layer does not seem to work very well with anything less than 512-sample transfer sizes. I tested all sizes between 128 and 512, and the 512-sample transfer size sounds best by a wide margin. It would be better to use smaller transfer sizes, as the larger the transfer size the bigger the delay between the game initializing audio playing and the time when the audio is actually heard. With the 512-sample buffer the delay is 23ms, which should still be small enough not to be noticeable.
The third problem was actually mostly fixed by fixing the second problem, using larger transfer sizes. I noticed that I got rid of the hangs by transferring the audio buffer in the same every fourth interrupt as where I handle the screen and keyboard stuff. It seems that sending the audio buffers too fast can hang the DSTwo I/O layer. I actually experienced an interesting hang once in Doom, where the screens stopped updating but the audio still continued, and based on the audio (gunshots and monster roars) the game continued fine in the background even when both the screens were completely frozen!
After I got those three problems fixed, I continued by adding the ADPCM sample routines for Duke Nukem 2. Those have now also been implemented, but for some still unclear reason Duke Nukem 2 seems to hang quite often. At times only the audio hangs and the game continues forward, at other times the whole system hangs. I have debugged the situation where the audio stops working, and in that situation the DSTwo I/O layer never releases the audio transfer buffer, and thus DS2x86 is unable to send the next transfer block. So, the problem is again somewhere in the DSTwo I/O layer, or more likely in some interaction between my emulation interrupt and the DSTwo I/O layer. It is rather frustrating to always fight with the DSTWo I/O layer to get rid of weird problems, but I suspect that is the price to pay for trying to bypass some limitations in the I/O layer.
Anyways, I got bored with debugging Duke Nukem 2, as Supaplex, Doom and Warcraft all now play nearly perfect SB digital audio and work fine without crashing for at least 15 minutes (that's the longest I have tested them). So, I started porting the AdLib audio code from DSx86 to DS2x86. The first step is to simply convert the assembler code from ARM to MIPS, and that is what I am currently doing. The bigger step is then to actually change the playing scheme. In DSx86 I could simply play each of the 9 AdLib channels using different NDS hardware audio channels, but in DS2x86 I need to mix all of these channels to the same output buffer that the SB digitized audio emulation uses. This might require some further changes to the AdLib emulation code, but I don't know for sure yet as I am now just converting the code. There is a tiny chance that the next version of DS2x86 might have AdLib audio, but it is more likely that the code does not work properly yet at that time.
Thanks again for your interest in DSx86 and DS2x86! The GBATemp Homebrew Bounty 2011 is currently in the voting phase, so I don't know yet whether DSx86 or DS2x86 will win anything. I am looking forward to seeing some results for that competition!
For more information and downloads, click here!
There are 0 comments - Join In and Discuss Here