Replace part of the SB 8-on/8-off buffer status with DMA buffer state
The eight on/off logic is split:
1. The off part is kept; we now report available room every 8th call.
2. The on part is replaced with an actual check of the DMA buffer state.
Previously, the SB's write buffer status could report "we're full,
we can't accept data" eight consecutive times, even when when the
DSP and DMA and totally idle. This behaviour causes SimLife and
the Crystal Dream demo to eventually give up on setting up their
DMA transfers.
So this replaces the "we're busy, we're busy, we're busy, ..." with
an actual check of the DMA status.. and if DMA isn't running or
at the minimum (about to run dry), then we say "we're available".
---
More notes from the PR review:
I created this based on logging what these games were doing (just
a bunch of LOGs in SB) combined with reading about the buffer
write check (in the ref'd docs).
86Box and DOSBox-X both calculate the playback rate of the current
DMA transfer and then steadily draining down that time until it
nears completion.
In this implementation, fortunately we've already got the dma.left
and dma.min values to see how close we are to the end of the
transfer.
One of the nuances is that games can change the playback rate as
well as switch from mono to stereo; so the drain down rate can
vary: and indeed, 86Box does this extra bookkeeping.
In our case, these rate changes are already taken care of by the
existing code, and it just happens that this threshold (dma.left
vs dma.min) will happen sooner if the rates are faster.
Scali had a through discussion about Sound Blaster seamless
playback using all the different DMA methods (autoinit, signle
init, and hacked methods) https://www.vogons.org/viewtopic.php?t=52806. He wrote a nice
seamless playback tool, too (tested this PR with it, and it's
fine).
Fix an undefined behaviour in the 32-bit signed multiplier
DIMULD ("double word integer multiplied with double word integer")
multiplies signed 32-bit values (op2 and op3), and stores the result
in op1, also a signed 32-bit variable.
The result of the multiplication is allowed to overflow the capacity
of op1; if so, then the carry and overflow bits are set.
The issue is that operands were being cast straight from their
incoming uint32_t register types up to signed 64-bit values, instead
of first being cast to signed 32-bit values.
This was caught while testing Frontier - First Encounter with a
position independent UBSAN build: