First post, by furrykef
I wasn't too sure which forum to put this in, so I'm putting it here. Please move as necessary. (Maybe it should go in one of the DosBox forums, but I'm also interested in how this behaves on a real Tandy...)
I'm writing an NSF player -- y'know, Nintendo music -- for the Tandy 1000 and IBM PCjr. So far this has worked splendidly except for one thing: the SN76489 kinda sucks at being a 2A03. Not only do the squares have no duty cycle and triangles are not possible, the bottom octave is missing. For the NES triangle channel, the bottom two octaves are missing, so many songs have no bass. I find this unacceptable.
So I thought, OK, I'll just set the frequency to zero and toggle the volume to play arbitrary waveforms manually. (This is not unlike the "RealSound" technology used for the PC speaker.) This almost works in DosBox 0.74. I wrote a test program that hooks the 1Ch (or 08h) interrupt and plays a square wave by toggling whenever the timer fires. The problem is, there are gaps in the square wave where the peak or trough is about twice as long as it should be. The result sounds like some kind of really bad arpeggio instead of a solid square wave. I have confirmed that DosBox is running literally nothing aside from my timer routine, so it's not like some other interrupt is interfering. Upon thinking about it further, I think what's happening is that the system clock is slightly out of sync with the SN76489's clock, so sometimes the volume is toggled before the SN's timer fires and sometimes it's toggled after, hence the apparent skips. However, I'm not 100% sure on this yet.
I'm trying to determine if this is a fault in DosBox or if the real hardware behaves this way. I have attached my test program below. (Note that there is no way to kill it, so it will require a reboot.) It would be nice if somebody could tell me how it sounds on a real Tandy. If a real Tandy produces a solid square, then we can work on fixing DosBox to produce the same result.
To assemble with NASM: nasm test.asm -fbin -o test.com
cpu 8086
org 0x100
section .text
start:
; Set up multiplexer
in al, 0x61
or al, 0x60
out 0x61, al
; Set channel freq to 0 (flat output)
mov al, 0x80
out 0xc0, al
xor al, al
out 0xc0, al
; Silence channel
mov al, 0x9F
out 0xc0, al
; Kill all interrupts for now
; (Note: in a real program, we'd need to restore them upon exit)
mov al, 0xff
out 0x21, al
; Install timer ISR
; NB: In actual program, will have to uninstall ISR afterwards
; Remember DS == CS, so DS:DX == CS:DX
;mov dx, ISR
;mov ah, 0x25 ; DOS service 0x25 = install ISR
;mov al, 0x08 ; 0x08 = system timer interrupt
;int 0x21
; Install timer ISR the hard way
xor ax, ax
mov es, ax
mov ax, ISR
mov [ES:8*4], ax
mov ax, cs
mov [ES:8*4+2], ax
; Set clock
; NB: In actual program, will have to reset clock when done
; (same code, just send 0's to 0x40 instead)
; Then also set DOS to real time clock
mov al, 0b00111010 ; @TODO@ -- which mode (bits 1-3)?
out 0x43, al
mov al, 0x70 ; Low bits
out 0x40, al
mov al, 4
out 0x40, al ; High bits
; Enable just the timer interrupt
mov al, 0xfe
out 0x21, al
; 'Forever' is brought to you by Pinkie Pie
forever:
jmp forever
ISR:
cli
push ds
push ax
; Set DS to CS
push cs
pop ds
mov al, [SquareToggle]
not al
mov [SquareToggle], al
and al, 0x0f
or al, 0x90
out 0xc0, al
pop ds
; Tell 8259A interrupt done
mov al, 0x20
out 0x20, al
pop ax
sti
iret
SquareToggle: db 0