A few years ago, I patched some of the code in Triton’s Crystal Dream demo from 1992:
Although my code made it work on my real 486DX2-80 machine with Sound Blaster Pro (the ‘ideal’ setup for this demo), it did not work in DOSBox at the time:
It still does not work properly in Dosbox though, but I think that is related to the way it uses DMA. It also won’t run properly under Windows 95, only from pure DOS. It might be because it does not reset the PIC timer rate to the default value… But I don’t know if and when I’ll ever look into that.
Well, the other day I was fixing some other bugs in DOSBox… namely, the screenwidth was hardcoded for the CGA composite emulation mode. I figured, since I had a working configuration to build the DOSBox source code anyway, I might as well give Crystal Dream another look.
As I said in the earlier blog, I thought the issue was related to the PIC, because DOSBox prints the following messages on the console during the demo:
PIC:ICW4: 1f, special fully-nested mode not handled
PIC:ICW4: 1d, special fully-nested mode not handled
PIC:ICW4: 1f, special fully-nested mode not handled
This turned out to be a wild goose chase however. I thought that the demo would use at least two interrupts, namely the timer interrupt, and the SB DMA interrupt. Since DOSBox cannot handle the special fully-nested mode, I thought perhaps some interrupts got lost.
This was not the case however. I opened up my old project, where I had reversed most of the sound playing code in order to fix the SB detection. And lo and behold: there was no interrupt handler at all for the SB!
Normally you’d get an interrupt when the DMA transfer is complete, so you set up a new buffer. So what is it exactly that Crystal Dream does then?
Well, one part of it is a rather curious DMA mode: It sets up a DMA transfer for 1 byte at a time, and uses the timer interrupt to replace this byte at the replay rate. This apparently works in DOSBox, because you can hear the first buffer playing (it sounds rather distorted though). However, the DMA then stops, while it continues playing on a real system. I looked into the SB emulation code of DOSBox somewhat, and found that if I forced the ‘autoinit’ DMA mode, that it would continue playing, without requiring an interrupt handler to restart the DMA. However, the code does not seem to set up the SB for autoinit, which makes sense, since only SB 2.0 and later support it, and this demo would target early hardware as well.
I happened to read about the single-byte DMA technique, when looking into DOSBox-X, a patched version of DOSBox aiming to emulate the hardware more correctly. It is known as ‘goldplay’ there, since Goldplay is an early MOD player which used this technique. I tried using ‘goldplay’ mode on Crystal Dream, but although it made the sound clean (regular DOSBox DMA code does not process the DMA quickly enough, so the sound is very noisy and distorted), it still stopped after the first buffer.
I looked into the code somewhat more, and noticed that there were some comments about Crystal Dream in the code. Apparently this issue has been looked at. And indeed, once I used the correct settings (the patches only apply for regular SB/SBPro configurations, not for the default SB16), DOSBox-X could play back the sound properly.
There is a document on how exactly Crystal Dream handles its Sound Blaster audio, and why it wouldn’t work in DOSBox: https://github.com/joncampbell123/dosbox-x/blob/master/NOTES/Triton%20-%20Crystal%20Dream.txt
Here is also a discussion on the issue and the fixes in DOSBox-X: http://www.vogons.org/viewtopic.php?f=41&t=31881&start=480#p362451
In short, it doesn’t rely on the SB’s interrupts at all. It simply polls the SB status and resets the playback from inside the timer interrupt.
While this works on real hardware, the DOSBox emulation wasn’t accurate enough to allow for such polling. But DOSBox-X contains some patches to handle the situation.
Pingback: DMA activation | Scali's OpenBlog™
Do you have any idea of the technical reason to use the single-byte DMA? I noticed the demo also has Covox support, which I imagine would have a very similar timer ISR (writing 1 byte to 1 location), so maybe this was an easy way to plug SB support into that code?
Yes, GoldPlay also uses this approach. It is probably because it’s a ‘lowest common denominator’ solution indeed. Covox and PC speaker can be supported this way, outputting one byte per timer interrupt. It’s not ideal for DMA-based devices, but the overhead is probably negligible on faster 286s and above, which the demo is targeted at, and it makes the code simpler. Of course there may be synchronization problems from time to time, but you probably won’t notice a single byte repeating or being skipped every now and then.