I am coding in old, 1994 era DOS for game programming fun, and am outputting data from a RAD file into the OPL3 FM Synthesis chip. I'm running off of DOSBox.
In a bid to increase performance when dealing with hardware registers (especially since I am updating the FM Synth chip through a timing interrupt (to keep tempo) and want to get back into main code as quickly as possible) I thought to rewrite my register outputting in assembly.
However, while this works, it seems to introduce distortion in the synth chip's output that isn't present when I use pure C's outportb, and I figured I'd ask here to see if anyone could tell me what's going on. Is there a particular, subtle difference between 'out dx,al' and 'outp(int, char)' that I'm missing?
OPLStatus status = NONE;
unsigned int primAd;
unsigned int secAd;
unsigned int backAd;
void OPLWriteImpl(void *argument, unsigned int registerNumber, unsigned char registerValue) {
(void)argument; /* Unused variable */
if(status == OPL3 || status == DUALOPL2) {
if(registerNumber <= REGISTER_THRESHOLD) {
outp(primAd, registerNumber);
outp(primAd+1, registerValue);
}
else {
outp(secAd, registerNumber);
outp(secAd+1, registerValue);
}
}
else {
outp(backAd, registerNumber);
outp(backAd+1, registerValue);
}
}
EXTRN status:WORD, primAd:WORD, backAd:WORD
.CODE
PUBLIC OPLWriteImpl
OPLWriteImpl PROC FAR C argument:DWORD, regnum:WORD, regval:BYTE
cmp status,0 ; if status == 0, go to OPL3
je three
cmp status,2 ; if status == DUAL, also go to OPL3
jne two ; otherwise, go to OPL2
three:
mov dx,primAd ; put OPL3 primary bank in dx
cmp regnum,100h ; check if regnum <=than threshold (256)
jle prim ; if so, go straight to primary bank
inc dx ; otherwise, writing to secondary bank (primary+2)
inc dx
prim:
mov ax,regnum ; put the register number in ax
out dx,ax ; output to register
inc dx ; go to data address
mov al,regval ; put the value in al
out dx,al ; output to data
jmp done ; finished
two:
mov dx,backAd
mov ax,regnum
out dx,ax
mov cx,6
loopOne:
in al,dx
loop loopOne
inc dx
mov al,regval
out dx,al
dec dx
mov cx,36
loopTwo:
in al,dx
loop loopTwo
done:
ret ; return to C
OPLWriteImpl ENDP
When using my C routine, the music outputs correctly as it was in RAD Tracker. But when I use my assembly routine, some of the instruments seem to have a strange grungy sound. I can't really blame Dosbox emulation because, like I said, it works just fine with pure C. The difference isn't super egregious, but it's more than noticeable. My only guess is that outportb's implementation does some magic that isn't present in my routine.
There is one main difference in the C and the assembler program:
I don't know your C compiler's library, but outp()
is either equivalent to out dx,ax
or to out dx,al
.
(I would guess that it is equivalent to out dx,al
.)
However, in your assembler program you are sometimes replacing outp()
by out dx,ax
and sometimes by out dx,al
. One of both must be wrong.
If a real sound card has an 8-bit ISA connector and outp()
is equivalent to out dx,al
, the following assembler code:
mov ax,regnum out dx,ax inc dx mov al,regval out dx,al
... taken from your assembler program equals the following C program:
outportb(address, regnum);
outportb(address + 1, regnum >> 8); // This may cause the noise
outportb(address + 1, regval);
Try to replace out dx,ax
by out dx,al
.
(Unfortunately I don't know if DOSbox simulates a sound card with an 8-bit or a 16-bit connector.)
Please note
After reading what I have written before, you might might try to use only one out
instruction to write register and value at the same time:
mov al,regnum
mov ah,regval
out dx,ax
With a high probability this would work with a real sound card with an 8-bit connector.
However, this might not work on real sound cards with a 16-bit connector (and maybe also not on a simulator like DOSbox).