• Please review our updated Terms and Rules here

Creative Music System (CMS) / Game Blaster compatible replica

Tronix

Experienced Member
Joined
Dec 14, 2012
Messages
138
Location
Russia, Moscow
Hello,

I read about Creative Music System (CMS) / Game Blaster at Great Hierophant blog. This sound card based on two Philips SAA1099 chips and looks easy to duplicate. Easier than adlib: SAA1099 data lines to ISA data lines, A0 to ISA A0, Vref through 10K resistor to VCC, DTACK (open collector output) not used and pull-up with 10K to VCC, CLK from ISA-OSC divided by two by 74ls74 or another counter. First SAA1099 chip CS to address decoder 0x220 and second SAA1099 chip to address decoder 0x222. Thats all!

But the most difficult - card autodetect. Great Hierophant write:
The left SAA-1099 is at I/O 2x0 & 2x1 and the right SAA-1099 is at I/O 2x2 & 2x3. The subsequent twelve I/O addresses are used by the CT-1302. For a long time, the function of this chip was a mystery, but now we know what it can do. One problem which Creative used this chip to solve was that of detection. While the game publisher could require a user to tell a program the hardware he had in his system, it would be difficult for a less savvy user to know or remember what he had or where in the I/O space it was. Creative decided to make it easy for the consumer and the game programmers by allowing the card to be detected by software.

In order for a card to be detectable in software in a PC, it has to give a reliable, non-random response to a read from the processor. The SAA-1099s cannot be read, only written, so there was no reliable way to detect these chips in software. Here is where the CT-1302 comes in. For the remaining addresses, 2x4-2xF, it will store or latch an 8-bit value written to it in one register, which is then read back by a program from a different register. If the values written and read match, then the card has been detected successfully. If they do not match, then the card is not detected. There must be two different I/O addresses involved, otherwise this scheme does not work reliably.

I tried to emulate this method in emulator - data written to 0x224-0x22F port is stored to variable and then read back when access to 0x224-0x22F port, but some programs did not detect card. Then I looked at the source code of DosBox and I found this logic: data store only when 0x226 or 0x227 port write. Other port ignored. Data read back only if access to 0x22a or 0x22b port. Other port ignored. And last thing - when reading port 0x224, data allways must contain 0x7F:

PHP:
uint8_t cms_port_read(uint16_t addr, void *p)
{
	uint8_t res;
	switch(addr-0x220) {
	case 0x4:
		res = 0x7f;
		break;
	case 0xa:
	case 0xb:
		res = lastwrite;
		break;
	}
        return res;
}

void cms_port_write(uint16_t addr, uint8_t val, void *p)
{
	switch(addr-0x220) {
	case 0x6:
	case 0x7:
		lastwrite = val;
		break;
	}

}

So, we need RAM to store 1 byte. I think i can use latch register. And i am looking the best way to do the decoder... Original card used 74ls138 2 pcs and 74ls139 1 pcs. At first approximation my circuit:

b6cf7632cffa4812b9250a526920e00d.jpg


/IOCS16 not used, this must be AEN signal..

Any ideas or suggestions?
 
Should be simple to do with a small CPLD or even a couple of GALs, no? For a one-byte memory TI/MMI made some LSTTL that looks like an LS374, but has read-back capabilities (74LS793/794), but those would be pretty hard to find now. TI also had some octal bidirectional bus registers (74LS651-4), but again, probably hard to find.
 
Should be simple to do with a small CPLD or even a couple of GALs, no?
Yes, you right, but i haven't programmer for GALs. Moreover, PAL/GAL programmer circuits too difficult to replicate at home.. CPLD like Altera EM7064 is a good choice, but i want keep a "retro" style, so DIP-packages more preferred...
 
Most PROM programmers will also do common SPLDs like 22v10s and 16v8s. Also allows you to stick with 5V PTH target.
 
- No need to decode A10 and A11 for I/O ports, most ISA cards don't...
- Probably no need to decode A0 either... Does it matter if both 0x224 and 0x225 will read as 0x7F?
- I'd try to avoid 74154 because it is slow and large. If possible use 74LS138 instead
- ~IOCS16 is not present on 8-bit part of ISA, and yes, I/O card needs to decode AEN signal (or bad things will happen with DMA).
- It might be possible to save U3 and all the related decode logic. Just put pull-ups/pull downs right on the internal data bus. TTL logic has relatively high "LOW" level input current (0.4-1.6mA depending on logic family). So the pull down resistor should be 1k or even less (470 ohm?!). The pull-ups can be 4.7K, or even 10K...
 
Most PROM programmers will also do common SPLDs like 22v10s and 16v8s. Also allows you to stick with 5V PTH target.

Be careful there--I've seen some Chinese programmers that list the 22V10 as one of the devices, but it turns out that they can handle only the 20-pin GALs, such as the 16V8. That's one of the reasons I sold one that I had just purchased.
 
Could be something like this:
Game Blaster Address Decode.jpg

The 74LS245 transceiver is enabled by addresses in 0x220-0x23F range (generated by the bottom 74LS138 ).
The lower bits of address (A5..A1) are decoded by the top 74LS138. It generates chip selects for SAA-1099 chips and read/write enable signals for the 74LS374 flip-flop (I think 74LS574 FF, or 74LS373 and 74LS573 latches can be used instead).
The pull-up and pull-down resistors drive the internal data bus (DB0..DB7) so the default value is 0x7F (that if nothing else drives it).
 
Thank you, but if it is possible put a bigger picture? I expect the arrival SAA1099 chips from China, during this time I can try to make autodetect...
 
Thank you, but if it is possible put a bigger picture? I expect the arrival SAA1099 chips from China, during this time I can try to make autodetect...

Sorry about the image size... for some reason the forum software insists on converting PNG image to JPEG. Attaching a ZIP file with the orignal image. If you really want I can send you KiCad schematic as well...
 

Attachments

  • Game Blaster Address Decode.zip
    30.2 KB · Views: 3
BTW, wouldn't it be simpler to hack a game and remove the detection code (and set it to a default value of 0x220, or use an environment variable)? It seems that the described detection method only works with the Game Blaster, but not with SB 1.5 / SB 2.0. So hacking the game will make it usable on these cards as well.
 
Today i get some time to build CMS replica... I used mostly Sergey's circuit for auto-detect part except 10k pull-ups. If i read any empty port i get 0xFF value, so i think motherboard have internal pull-ups for data lines. Soldered auto-detect part from Sergey's circuit:

b8c0abf7f9834033b09239dcba2fee72.JPG


Did the test stand based on 386 CPU and install my device onto left ISA-slot:

7b81d951cec84287a50e68e6948edd6f.JPG


And run TESTCARD.EXE from CMS diskette. So, this software successfuly found CMS:

fb8a623cbd7e419996ff1ba705a4f11a.JPG


Then i soldered two SAA1099 and 74HC74 for divide 14,3MHz by two. Audio outputs has not filtered, no amplifier. Just 1k pull-ups for all four output channel and then mixed by 10k resistive divider into left and right. Then output to headphones over 0,1mF:

041e153050c9409ba0bc5e3f6f87a8ac.JPG


It's worked! noisy, but work! :D I think needs to be done with the use of filter capacitors.

I recorded sound from this card:

 
Sounds very promising, and I would think that if it replicates DOSBox's CMS detection methods, then it would work with every game that requires the Game Blaster autodetection to work.
All you Russians, first the Adlib, then the SSI-2001 and now the Game Blaster! Perhaps the Covox Sound Master will be next. Will no 8-bit sound card be kept exclusive and ludicrously expensive? :)
 
Last edited:
Nice work!

Audio quality: Adlib and SoundBlaster use active filters. Also the audio part is separated from digital, shielded (at least has a solid ground plane).

Not sure if reading an unused I/O address will always give 0xFF. Older computers (e.g. PC/XT) normally don't have pull-ups on data bus. And 0xFF might be a side effect of TTL inputs reading '1' when connected to Hi-Z state outputs. So I recommend put these pull-up resistors.

It is interesting to see some soviet/Russian chips on your board.
BTW, K155TM2 is an old plain TTL 7474; K555 series are 74LS analogs; K1533 are 74ALS.
 
Thank you all for your support!

It is time to develop some software support... Adopting experience of Innovation SSI-2001 MIDI fun, I tried to do the same thing with CMS. I downloaded source code of Miles Sound System Version 2 (AIL2) from John Miles site and modify Tandy driver (SPKR.INC) to work with Creative Music System card. Only the default IO address (220h) is currently supported.

I used mostly source code of Paku-Paku game by Jason M. Knight (sound library). Also very helpful this threads: Creative Music System (C/MS)??? for CMS_Programming_Information.zip and this Game Blaster / CMS / SB + CMS owners, please help test But it's not a simple "copy-paste". In Paku-paku CMS sound write procedure i found some mistakes or bugs. For example, overflow when "set frequensy" command calculating; bad logic in "set octave" command calculating; reading CMS port and then ORed with new value for channel enable, but CMS port are write only etc..

So, beta-version of Miles CMS.ADV driver in attachment (with source code). I am tested it with perfect and simple MIDI and XMIDI player PX-Palyer in DosBox (set "sbtype=gb" in dosbox.conf file) because i not have the time to check on the real hardware. Sound quality so-so, but its worked. Driver not used noise generator functions and very simple at all.

Right now CMS can support 150+ games, in theoretical ;)
 

Attachments

  • cms_miles.zip
    42.7 KB · Views: 1
Last edited:
If it helps any to tweak it in better, I've updated my CMS library so that it works with a wider range of devices -- some of the GAL's used in certain versions of the card don't entirely work the same, so these changes get rid of the last few CMS equipped SB's that either didn't make sound, or output nonsense.

Code:
; 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

Hope that helps. Mind you, it's only designed for three voice, so the octaveStore buffer would need to be enlarged.

Basically I noticed all the same bugs you did and fixed them back in October.
 
I decided to probe my Game Blaster using port reads and writes via debug. I found that 0x224 reads either 3F or 7F depending on the system, and that 0x225 behaves identically to 0x224. Values written to 0x226 and 0x227 appear at 0x22A and 0x22B. Other addresses read usually as FF and writing to them does not appear to change anything elsewhere. The 3F value was seen in a Tandy 1000 TL, the 7F in a generic 486 PC.

All the games requiring a Game Blaster and the Creative drivers/utilities work fine with a Game Blaster in my 1000 TL, so I guess it isn't that important.
 
Last edited:
Today i found KA2206N stereo amplifier (TEA2025 analog). I solder this chip to my board. I used reference circuit from KA2206 datasheet.

6e357085091f49a882e821fdb5c3bfac.JPG


Then i solder filter 0,01mF capacitors from Sam Coupe SAA1099 circuit:

f527de375aa74150b0ede8c7e0ac520f.JPG


Sound still not clean, digital noise present. I think it's because of the tangle of wires. Originally under the chips have a large ground polygon. I leave everything as it is. Some new records from board:

 
Back
Top