You are on page 1of 4

Sound cards which are Sound Blaster-compatible (i.e.

which support the specifications that were present in the original, actual Sound Blaster spec) are capable of playing raw sound effects directly, without need to set up an IRQ or DMA channel. This was not usually done in games, since it meant that the game couldn't do very much besides pay attention to the sound effects, which isn't usually useful in a game which has to be doing lots of other things besides playing sound, but if you really just want to play anything with your sound card, and don't care about doing anything else at the same time, this sort of works. For more information on direct sound with the Sound Blaster, and why you might (or might not) want to use it, see The Two Ways You Can Play Digitized Sound On A Sound Blaster. But this article is not about why; this article is about how. This article assumes you already know that you want to play direct sound on a Sound Blaster, and are simply interested in finding out how you can do so. There is a command available on the Sound Blaster for direct DAC (Digital-to-Analog Conversion) which takes a digital sound waveform and converts it into an analog sound. What does digital sound look like? It simply is a string of data values which represent the level of a sound waveform at each sample point. Digital sound is made up of a series of samples, and each sample is just a number representing what level the sound waveform is at during that point in time. So, for example, a square wave could be represented as the following sequence of bytes:
00000000 00000000 00000000 11111111 11111111 11111111

Each low point and high point of this square wave is 3 bytes long. You can speed up the square wave by reducing each point to only 1 or 2 bytes, or you can slow it down by adding more bytes to each portion. Before we get started on actually writing a program that'll play raw sounds, we might want to think for a moment about where we're going to get our sound data from, and how we'll represent it in a form the program can read. What we need is raw sound data; as it turns out, a regular WAV file, similar to the type used by more recent versions of Windows (versions after Windows 3.0), is *almost* a raw sound file; a WAV file has a small header that contains some basic data about the file, but after the header it's almost pure raw sound data. You could quite conceivably just record a WAV file, strip off the header, and end up with a perfectly good raw sound file. If you're going to do this, save the file as a PCM-format, 8-bit mono file. You could, also, just type up a long string of bytes to represent sound data and insert it into your program code as a literal data string, but for the purposes of this program, let's assume that you already have a raw sound in a filename called SOUNDATA.RAW, and that we're going to open this file with our program to retrieve the sound data. Since we're going to use a file for our data, the first thing we do with our program should probably be to open this file and put it into a memory buffer. We can do this with lines of code similar to the following:
MOV MOV MOV MOV INT ;AX AH,3Dh ;open file using handle AL,0 ;read-only DS,SEG filename DX,OFFSET filename 21h should now be the file handle!

MOV BX,AX ;transfer file handle to BX where it belongs MOV AH,3Fh ;read file using handle

The hexadecimal value of this command is 0D1h.OFFSET databuff INT 21h . we want to establish the base address of the Sound Blaster. (This I/O port behaves completely differently depending on whether you read from or write to it. it'll just mean your program will occupy more memory than it needs. ASCII zeroterminated) value "soundata. the program will run those data bytes as instructions. AX should contain a handle which points to our newly-opened file. Note that these two lines should go at the very end of your program. adjust this to the actual size of the sound file you're using. though.SEG databuff MOV DX. where we've stored the ASCIIZ (i. When this function is done running. change the next line as appropriate: BASEADDR EQU 0220h The second thing we want to do is establish a subroutine for waiting after sending a command to the Sound Blaster. and it's sort of important that we use it. the Sound Blaster is done with whatever you told it to do. and it may take a few cycles for it to do what you ask of it. I tend to call this subroutine WAITWRITE. For this reason. Before we send any commands to the Sound Blaster. This is almost always 0220h.8000h .) The final two lines form the actual raw data locations in the program.al js loopwait . If you got something other than a 0. and is ready to receive new commands. and transfer 8000h (32. But don't let the file-reading operation go too far.3F.) 3. First. The procedure goes like this: 1. but if you have a different value for your SB. since that was the factory-default value and hardly anybody ever changed it. it won't hurt anything if you make databuff too large.dx or al. Send the command to the Sound Blaster by outputting a command byte to BASEADDR+0Ch.768 decimal) bytes from this file into a data buffer we've created in memory which is simply called "databuff". There's actually a command to do this on the Sound Blaster.databuff should now hold the first 8000h bytes of the file! FILENAME DB 'soundata. make sure CX isn't too large when you call INT 21. then by all means. doesn't react to input instantly. it makes sense to make a nice little subroutine for this. WAITWRITE: .e. 2. If the data from the read returns a 0.raw'. return to step 2. like almost any other electronic device. otherwise.MOV CX. The Sound Blaster. We've set the function to look for a filename in the data location named "FILENAME". The second block of code uses INT 21. Before sending any additional commands. Since we need to do this after sending every single command to the Sound Blaster. Note that this figure of 32.number of bytes to be read MOV DS.raw". READ from BASEADDR+0Ch. (Actually. not opcodes! Now let's turn on the speakers.BASEADDR+0Ch loopwait: in al. there's a mechanism to determine when the Sound Blaster has finished doing whatever you told it to do. since otherwise the speakers will stay off and we won't be able to hear anything. which isn't good! Those bytes are data.3F to read data from our now-open file.768 bytes is arbitrary. and it's ready to receive new commands.3D. we want to do two things. or it might go clear past the end of the file and start reading bytes that don't belong to the sound file! In other words. 00 databuff DB 8000h DUP(?) The first block of code opens a file using INT 21.Waits for Sound Blaster to be ready before sending it more data MOV DX.

RET Once this function is inserted into your program.3Fh . All you need now is a raw sound file.Speakers are now on!!! MOV SI.3Dh .AX . like this one.08000h .databuff should now hold the first 8000h bytes of the file! CALL WAITWRITE . Call WAITWRITE. you can simply send it a command on port BASEADDR+0Ch.AL CALL WAITWRITE . the more cycles per second a computer needs to be running at to play it at the correct speed. BASEADDR EQU 0220h .send commands.OFFSET databuff . Not too bad. Note that.SEG databuff MOV DX. or (probably an easier way if you just want to hear the sound) running the program in DOSBox and cranking the cycles in DOSBox to a very low setting. MOV AL.read-only DS.SEG filename DX.Turn speakers on OUT DX. Send 10h to BASEADDR+0Ch.0 .First. 4. predictably. This can be done by either inserting some kind of delay loop into the program. Once you're sure that the Sound Blaster is ready to accept a new command. PCM-format WAV file.AX AH. (Actually. so we can freely start sending commands there once WAITWRITE .000 hertz.number of bytes to play playloop: . the process for playing a raw sound from this point forward contains 4 simple steps: 1. The command for direct DAC is 10h.0D1h .Sound Blaster base I/O address . that's an 8. but the WAV file's header at the beginning of the file is so small that you hardly even notice the brief blip of sound it makes before the actual sound plays. Call WAITWRITE.number of bytes to be read MOV DS.read file using handle MOV CX.transfer file handle to BX where it belongs MOV AH. you can call it at any time just by using the line call WAITWRITE. Send a raw sound byte to BASEADDR+0Ch. chances are your computer will play the sound *way* too fast for it to be at all recognizable. 8-bit.OFFSET filename 21h should now be the file handle! MOV BX.returns. 2. Do this after every command you send to the Sound Blaster to help keep your Sound Blaster working. and this byte must be sent for EACH raw sound byte you want to send.This call to WAITWRITE has already initialized DX to the right place to .OFFSET databuff INT 21h .). mono. You'll need to slow down the rate at which the sound samples play. let's open our file: MOV MOV MOV MOV INT .open file using handle AL. 3.Point SI to the sound data MOV CX.8000h . Note that if you just run this program directly. right? Here's a full assembler program that you can compile and run. the higher the sampling frequency of a raw sound file. So basically.

This plays the sound! CALL WAITWRITE loopnz playloop .This keeps going through playloop for CX times mov ah...Direct play.AL CALL WAITWRITE MOV AX.AX .raw'.MOV AL.[SI] .004C .dx or al.Actual sound data value INC SI .Increment SI for the next sound data byte we play OUT DX.terminate program int 21h WAITWRITE: . 00 databuff DB 8000h DUP(?) .Waits for Sound Blaster to be ready before sending it more data MOV DX.al js loopwait RET FILENAME DB 'soundata. Must be done for each byte! OUT DX.10h .BASEADDR+0Ch loopwait: in al.