; Creative Music Source dual SA1099 Sound support
; Jason M. Knight, October 2014
; Intended for compilation with NASM
; labels starting with "i_" are for local/internal use only
BITS 16
CPU 8086
%include "TURBOPAS.MAC"
segment CONST
; CMS registers are read only, so we have to buffer these ourselves!
cmsOctaveStore dw 0x0000, 0x0000, 0x0000
cmsFreqBits dw 0x0000
cmsNoiseBits dw 0x0000
cmsFreqMap: ; convert 0..243 to 0..255 with SAA1099 freq curve
db 0, 2, 3, 5, 7, 9, 11, 13
db 15, 17, 19, 21, 24, 26, 28, 30
db 32, 33, 35, 37, 39, 40, 42, 45
db 47, 48, 50, 51, 53, 55, 57, 58
db 60, 61, 63, 65, 66, 67, 68, 70
db 71, 73, 74, 76, 77, 79, 80, 82
db 83, 85, 86, 87, 88, 90, 91, 93
db 95, 96, 97, 98, 100, 101, 103, 104
db 105, 106, 108, 109, 111, 112, 113, 114
db 115, 116, 118, 119, 120, 121, 123, 124
db 125, 126, 127, 129, 130, 131, 132, 133
db 134, 135, 136, 137, 138, 140, 141, 142
db 143, 144, 145, 146, 148, 149, 150, 151
db 152, 153, 154, 155, 156, 157, 158, 159
db 160, 161, 162, 162, 163, 164, 165, 166
db 167, 168, 170, 171, 172, 173, 174, 174
db 175, 176, 177, 178, 179, 180, 181, 182
db 182, 183, 184, 185, 186, 187, 188, 189
db 190, 191, 192, 193, 193, 194, 195, 196
db 196, 197, 198, 199, 200, 200, 201, 202
db 203, 203, 204, 205, 206, 206, 208, 208
db 209, 209, 210, 211, 212, 212, 213, 213
db 214, 215, 216, 217, 217, 218, 218, 219
db 219, 220, 221, 222, 222, 223, 224, 225
db 225, 226, 226, 227, 227, 228, 228, 229
db 230, 231, 232, 232, 233, 233, 234, 234
db 235, 235, 236, 236, 237, 238, 239, 239
db 240, 240, 241, 241, 242, 243, 243, 244
db 244, 245, 245, 246, 246, 247, 247, 248
db 249, 250, 250, 251, 251, 252, 252, 253
db 253, 254, 254, 255
segment CODE
; let's do the math here instead of during execution
%define cmsFreqMin 32
%define cmsFreqMax 7823
%define cmsFreqLo 245
%define cmsFreqHi 489
%define cmsFreqMapOffsetMinusLo cmsFreqMap - cmsFreqLo
extern cmsSoundPort
i_WriteByteBoth:
; INPUT
; dx = soundport
; al = register
; ah = data
; PRESERVES
; all
add dx, 3
out dx, al
dec dx
xchg ah, al
out dx, al
dec dx
xchg ah, al
out dx, al
dec dx
xchg ah, al
out dx, al
xchg ah, al
ret
i_WriteWordBoth:
; INPUT
; dx = soundport
; al = register
; bx = data
; PRESERVES
; all
add dx, 3
out dx, al ; oAL
dec dx
xchg bl, al ; BL = oAL, AL = oBL, BH = oBH
out dx, al ; oBL
xchg bh, al ; BL = oAL, AL = oBH, BH = oBL
out dx, al ; oBH
dec dx
xchg bl, al ; BL = oBH, AL = oAL, BH = oBL
out dx, al ; oAL
dec dx
xchg bh, al ; BL = oBH, AL = oBL, BH = oAL
out dx, al ; oBL
xchg bl, al ; BL = oBL, AL = oBH, BH = oAL
out dx, al ; oBH
xchg bh, al ; original order.
ret
i_CmsSelectPort:
; INPUT
; [bp + 6] == voice 0..11
; OUTPUT
; bx = cms register (0..5)
; dx = port (0x02?1 or 0x02?3)
mov bx, [bp + 6]
mov dx, [cmsSoundPort]
inc dx
cmp bx, 6
js .done
sub bx, 6
inc dx
inc dx
.done:
ret
i_CmsFindBits:
; INPUT
; al = register to set
; bx = voice register 0..5
; dx = chip register port (typically 0x02?1 or 0x02?3)
; OUTPUT
; al == bitmask
; bx == address of appropriate cmsFreqBits byte
; dx == chip data port (typically 0x02?0 or 0x2?2)
; CORRUPTS
; cl
out dx, al
dec dx
mov cl, bl
mov al, 1
shl al, cl
mov bx, cmsFreqBits
test dx, 0x0002
jz .done
inc bx
.done:
ret
i_CmsSetAL:
; INPUT
; al = bit mask
; bx = address of cmsBits byte
; dx = chip data port
or al, [bx]
mov [bx], al
out dx, al
ret
i_CmsMaskAL:
; INPUT
; al = bit mask
; bx = address of cmsBits byte
; dx = chip data port
; CORRUPTS
; al (masks off top two bits)
not al
and al, 0x3F
and al, [bx]
mov [bx], al
out dx, al
ret
i_SetFreq:
; INPUT
; ah = freq
; bx = voice 0..5
; dx = port 0x02?1 or 0x02?3
; OUTPUT
; none
; CORRUPTS
; al
mov al, bl
or al, 0x08
out dx, al
dec dx
mov al, ah
out dx, al
inc dx
ret
i_SetOctave:
; INPUT
; ch = octave
; bx = voice 0..5
; dx = port 0x02?1 or 0x2?3
; OUTPUT
; none
; CORRUPTS
; al, bx
mov al, bl
shr al, 1
or al, 0x10
out dx, al
dec dx
shr bx, 1
mov al, [bx + cmsOctaveStore]
jc .setHigh
; setLow
and al, 0xF0
jmp .done
.setHigh:
and al, 0x0F
mov cl, 4
shl ch, cl
.done:
or al, ch
out dx, al
inc dx
mov [bx + cmsOctaveStore], al
ret
; procedure cmsReset
pProcNoArgs cmsReset
mov ax, ds
mov es, ax
xor ax, ax
mov di, cmsOctaveStore
stosw
stosw
stosw
stosw ; cmsFreqBits
stosw ; cmsNoiseBits
mov dx, [cmsSoundPort]
mov cx, 0x20
.loopZero:
call i_WriteByteBoth
inc al
loop .loopZero
mov al, 0x1C
mov bx, 0x0102
call i_WriteWordBoth
mov al, 0x15
call i_WriteByteBoth
dec ax
call i_WriteByteBoth
inc ax
inc ax
call i_WriteByteBoth
mov cx, 6
mov ax, 0x8800
.loopVolume:
call i_WriteByteBoth
inc al
loop .loopVolume
retf
; proceudre cmsSetAmplitude(left, right, voice:word);
pProcArgs cmsSetAmplitude
call i_CmsSelectPort
mov al, bl
out dx, al
mov al, [bp + 8]
mov cl, 4
shr al, cl
add al, [bp + 10]
dec dx
out dx, al
pRet 6
; procedure cmsEnableFreq(voice:word);
pProcArgs cmsEnableFreq
call i_CmsSelectPort
mov al, 0x14
call i_CmsFindBits
call i_CmsSetAL
pRet 2
; procedure cmsDisableFreq(voice:word);
pProcArgs cmsDisableFreq
call i_CmsSelectPort
mov al, 0x14
call i_CmsFindBits
call i_CmsMaskAL
pRet 2
; procedure cmsEnableNoise(voice:word);
pProcArgs cmsEnableNoise
call i_CmsSelectPort
mov al, 0x15
call i_CmsFindBits
call i_CmsSetAL
pRet 2
; procedure cmsDisableNoise(voice:word);
pProcArgs cmsDisableNoise
call i_CmsSelectPort
mov al, 0x15
call i_CmsFindBits
call i_CmsMaskAL
pRet 2
; procedure cmsSetFreq(freq, voice:word);
pProcArgs cmsSetFreq
call i_CmsSelectPort
mov ah, [bp + 8]
call i_SetFreq
pRet 4
; procedure cmsSetOctave(octave, voice:word);
pProcArgs cmsSetOctave
call i_CmsSelectPort
mov ch, [bp + 8]
call i_SetOctave
pRet 4
; function cmsSetHz(hz, voice:word);
pProcArgs cmsSetHz
; first let's turn off this voice
call i_CmsSelectPort ; bx == register 0..5, dx == 0x2?1 or 0x2?3
mov di, bx
mov si, dx
mov al, 0x14
call i_CmsFindBits
call i_CmsMaskAL
; determine freq and octave from hz
mov bx, [bp + 8]
cmp bx, cmsFreqMin
jl .exit
cmp bx, cmsFreqMax
jg .exit
mov ch, 3
; I HATE "WHILE" LOGIC!!!
.loopDownOctave:
cmp bx, cmsFreqLo
jg .loopUpOctave
shl bx, 1
dec ch
jmp .loopDownOctave
.loopUpOctave:
cmp bx, cmsFreqHi
jl .calcFreq
shr bx, 1
inc ch
jmp .loopUpOctave
.calcFreq:
mov ah, [bx + cmsFreqMapOffsetMinusLo]
; AH = freq, CH = OCTAVE
mov bx, di
mov dx, si
call i_SetFreq
call i_SetOctave
; turn this voice back on.
mov bx, di
mov al, 0x14
call i_CmsFindBits
call i_CmsSetAL
.exit:
pRet 4