;--------------------
; From: Jim Roberts
; Subject: SB Programming Info
The following contains info on how to program the SB card. There is
some source code which I used in my MOD file player, so I know it works.
Here are all of the usefull Sound Blaster - Sound Blaster 16 DSP
commands I know about to play sound:
==========
10h 8-bit direct mode output
Output : 10h, bData
Remarks: Output one unsigned byte unsigned to the DSP.
You are responsible for
controlling the sampling rate.
Cards : SB, SB2.0, SBPro, SB16
==========
14h 8-bit single-cycle DMA mode output
Output : 14h, wLength.LowByte, wLength.HighByte
Remarks: Output unsigned data using the DSP's
Single-cycle DMA mode. The DSP will generate
and interrupt after each block is
transferred. wLength is a word
giving the number of 8-bit samples minus 1.
Cards : SB, SB2.0, SBPro, SB16
=========
1Ch 8-bit auto-init DMA mode output
Output : 1Ch
Remarks: Output unsigned data using the DSP's
auto-init DMA mode. The DSP will generate
an interrupt after each block is transferred.
Set Block Size with the 48 command.
There are 2 ways to terminate auto-init DMA:
1. Program single-cycle DMA mode output.
2. Send command DAh.
Cards : SB2.0, SBPro, SB16
==========
40h Set digitized sound transfer Time Constant
Output : 40h, bTimeConstant
Remarks: Set the I/O transfer Time Constant.
Time Constant is the sampling rate representation
used by the DSP. It is calculated as:
TC = 65536 - (256,000,000/(channels * sampling rate)
Channels is 1 for mono, 2 stereo
Only the high byte of TC is sent to the DSP.
Cards : SB, SB2.0, SBPro, SB16
=========
41h Set digitized sound sampling rate
Output : 41h, wRate.HighByte, wRate.LowByte
Remarks: Valid sampling rates are 5000 to 45000Hz.
You don't need to multiple the sampling rate
by 2 for stereo.
Cards : SB16
=========
48H Set DSP block transfer size
Output : 48h, wSize.LowByte, wSize.HighByte
Remarks: The DSP will generate an interrupt after
transferring the block. wSize
is a word giving the number of bytes to
transfer minus 1.
Cards : SB2.0, SBPro, SB16
=========
90h 8-bit high-speed auto-init DMA mode
Output : 90h
Remarks: The DSP will generate an interrupt after
transferring the block of data.
Set the Block Size with the command 48h.
In high-speed mode, the DSP will not accept
any other commands. To terminate high-speed
mode, reset the DSP.
Cards : SBPro
wLength is a word giving the number of 16-bit
samples less 1.
Refer to 1Ch on how to terminate auto-init mode.
Cards : SB16
==========
CXh 8-bit DMA mode
Output : bCommand, bMode, wLength.LowByte, wLength.HighByte
Remarks: See command BXh. The only difference is that the
bCommand has a Ch in the high nibble instead of
a Bh.
Cards : SB16
==========
D0h Pause 8-bit DMA
Output : D0h
Remarks: After receiving this command, the DSP will stop
sending out DMA requests. Use for both
single-cycle and auto-init DMA modes.
Cards : SB, SB2.0, SBPro, SB16
==========
D1h Turn Speaker On
Output : D1h
Remarks: Turns the speaker on. Some notes:
1. On SB this command will pause the DMA.
2. On SB16 this command doesn't do anything.
Cards : SB, SB2.0, SBPro, SB16
==========
D3h Turn Speaker Off
Output : D3h
Remarks : turns the speaker off. See command D1h notes.
Cards : SB, SB2.0, SBPro, SB16
==========
D4h Continue 8-bit DMA
Output : D4h
Remarks: Resumes the DMA after a D0h command.
Cards : SB, SB2.0, SBPro, SB16
Output : D6h
Remarks: Resumes the DMA after a D5h command.
Cards : SB16
==========
D9h Exit 16-bit auto-init DMA
Output : D9h
Remarks: Exits ate the end of the current 16-bit DMA
transfer.
Cards : SB16
==========
DAh Exit 80bit auto-init DMA
Output : DAh
Remarks: Exits at the end of the current 8-bit DMA
transfer.
Cards : SB2.0, SBPro, SB16
===========
E1h get DSP Version Numbre
Output : E1h
Remarks: After sending this command, read two bytes
from the DSP. The first byte is the major
version and the second byte is the minor version.
Major Version Number detects cards:
01 - SB
02 - SB2.0
03 - SBPro
04 - SB16
Cards : SB, SB2.0, SBPro, SB16
;------------------------
; SB DSP Reset
Resetting the DSP - you must do this before using the DSP. The steps are:
1. Write 1 to port 2x6h and wait 3 microseconds.
2. Write 0 to port 2x6h.
3. Loop until you read a 0AAh from the DSP or 100
microseconds goes by.
mov dx,[SBADD] ;This needs to be your SB card's
;address. Don't prompt for the
;address. Just loop from 210h
;to 280h by 10h. Just start
;trying to reset until you find
;the right address.
mov al,1 ;Start the DSP reset.
add dx,6
out dx,al
mov cx,40 ;Wait 3 microsec.
DSPLoop:
in al,dx
loop DSPLoop
mov al,0 ;Stop the DSP reset.
out dx,al
add dx,8 ;Check for AAh response.
mov cx,100
CheckPort: ;See if the DSP port is
in al,dx ;ready to be read.
and al,80h
jz PortNotReady
sub dx,4 ;DSP port is ready to be
in al,dx ;read. Read and check for
add dx,4 ;an AAh.
cmp al,0AAh
je GoodReset
PortNotReady: ;Keep trying for upto
loop CheckPort ;100 micro seconds.
BadReset: ;There is not a good SB card
;at this address.
1. Make sure DSP write buffer is empty by checking bit 7
of port 2xc. If it is zero then you can write.
2. Write to the DSP.
mov dx,[SBADD] ;Get your card address.
add dx,0ch
SBLoop: ;Wait for write port to be clear.
in al,dx
and al,80h
jnz SBLoop
mov al,[Byte_To_Write_To_DSP] ;Write the data.
out dx,al
This is how to read from the SB DSP.
1. Make sure there is data available. This is done
by checking port 2xEh. If bit 7 is 1, then there
is data to read.
2. Read the data.
mov dx,[SBADD] ;Get the SB address.
add dx,0eh
SBLoop: ;Wait for data to be available.
in al,dx
and al,80h
jz SBLoop
sub dx,4 ;Get the data. (It's in AL.)
in al,dx
This is how to ack a DSP interrupt. (Tell the DSP that you
have processed the interrupt.) 1. Just read port 2xEH on
the DSP chip. mov dx,[SBADD] add dx,0eh in al,dx
Oh, and the actual interrupt vector you have to hook is 8h
plus the SB IRQ. So if you SB is set to IRQ2 then you
would hook int Ah.
You will also have to enable the interrupt on the PIC.
mov cl,[SBIRQ]
mov ah,1
shl ah,cl
not ah
in al,21h
mov [OriginalPIC],al
and al,ah
out 21h,al
;------------------------
; Subject: SB Modes
For whomever is interested, this is some stuff I leared about the Sound
Blaster family of cards while writing a MOD file player which will work
on the SB, SB2.0, SBPro, and SB16. It took forever to find out all of
this stuff so I thought I would save you the trouble.
There are basically three modes for playing data out of the SB. Direct,
single-cycle, and auto-init. Basically, direct sounds bad, single-cycle
is better, and auto-init is best.
Direct Mode:
Direct mode is the way Fast Tracker by Mr. H outputs sound on the sound
blaster. It basically sounds like crap (Fast Tracker is still a good
program though). In direct mode you directly write every byte to the SB
card. You control the sampling rate and that is why it sounds so bad.
The most common way to control the sampling rate is to reprogram the
timer chip to your sampling rate. Then every time a timer interrupt
occurs, you output the next byte to the SB card. The problem is that the
timer interrupt doesn't always get serviced right away (especially if
EMM386 is running). So your sampling rate is not very even. That causes
alot of hiss in your output. But if you like that sound, here is how
you can do it.
1. Reprogram the PIT (Programmable Interval Timer) for your
sampling rate. Compute Count as follows:
Count = 1,193,180 / SamplingRate
cli
mov al,36h
out 43h,al
jmp $+2
mov al,Count.LowByte
out 40h,al
jmp $+2
mov al,Count.HighByte
out 40h,al
jmp $+2
sti
2. Hook interrupt 8h.
3. Whenever you get an int 8h output a byte using the
DSP 10h Command.
4. Turn the speaker on with DSP command D1h
5. Sit back and enjoy the hiss.
Single-Cycle Mode:
Single-Cycle Mode if far superior to Direct Mode - but you will probably
only want to use it only on the original SB card. The reason for this is
because the SB2.0, SBPro, and SB16 all support Auto- Init Mode and the
SB doesn't. (Auto- Init Mode is just as easy to program and sounds the
best.) The way Single- Cycle Mode works is that you program the DMA to
send information to the sound card and you program the DSP to receive
and play that information at a specific sampling rate. Then that whole
block of information is sent to the sound card and when the sound card
runs out of info it will generate an interrupt and you just start
another one of these transfers. The block size can be up to 64K which
will give you about 4 seconds at 16KB sampling rate mono, or 2 seconds
stereo. The problem with this mode is very similar to the one in Direct
Mode. Namely, the sound stops while you service the interrupt. This
causes a popping or a crackling sound every time the DSP generates an
interrupt. Even if you precaculate your next sound buffer and write the
worlds fastest interrupt routine to start the next transfer you will
still get a pop. (Especially if EMM386 is running!) This is because
sometimes the interrupts get shut off for a while and you will not get
to service it until they are turned back on. But if you only have a
plain SB, or if you like to hear a very consistent poping sound, this is
the mode for you.
1. Allocate space for the DMA buffer (We will use 8K.)
2. Hook the interrupt vector for the DSP.
3. Enable the DSP interrupt on the PIC.
4. Set the sampling rate and voice volumes.
5. Fill the DMA buffer.
6. Program the transfer
7. When you get an interrupt goto 5 if you want to play more.
1. We need to allocate space for the DMA buffer, but it is not as easy
as you would think. This buffer cannot cross a physical 64K boundary.
Physical 64K boundaries have an address of X000:0000 (for real mode, if
you can program in protected mode then you don't need help figuring out
the address!) So the easiest was to do this is to allocate twice as much
buffer as you need and find a section of it which resides in one
physically 64k block. This is how I do it and it seems to work well as
long as your buffer size is divisible by 16:
DMABufferSize equ 8192 ;How big you want your buffer.
DMABuffSeg dw ? ;Handle to the memory block.
DMABuff dw ? ;Where in the memory block
;your DMA buffer starts.
;This is a segment, not an
offset!
mov bx,DMABufferSize ;Allocate memory for the
mov cl,3 ;DMABuff. DMABuffSize / 8
shr bx,cl ;gives you twice as much space
;as you need.
mov ah,48h ;Allocate the mem for DOS.
int 21h
jnc GoodMem
jmp Exit
GoodMem:
mov [DMABuffSeg],ax
mov [DMABuff],ax
and ax,0fffh ;See if you just happened to get
jz ClearBuff ;some memory which is exactly on
;a physical 64k boundary.
mov ax,[DMABuff] ;Find the next highest physical
and ax,0f000h ;64K segment.
add ax,1000h
mov dx,ax ;Save it away.
sub ax,[DMABuffSeg] ;See if DMABuff can fit between
cmp ax,(DMABuffSize/16) ;DMABuffSeg and the next highest
jae ClearBuff ;physical 64K segment.
mov [DMABuff],dx ;No, then use the next highest
;physical 64K segment.
ClearBuff:
mov es,[DMABuff] ;Initialize the buffer to
xor di,di ;8080h which is no sound
mov cx,DMABuffSize/2 ;for the SB cards.
cld
mov ax,8080h
rep stosb
2. Hook the interrupt vector for the DSP. The interrupt vector you will
use will be 8h plus the SB IRQ. For example if your SB card is on IRQ2,
then you want to hook int 0Ah. This is how I did it:
SBIRQ db ? ;You'll have to figure out what to put
;in here. Please don't ask the user
;what interrupt vector there card is
;on. Have your program figure it out
;using trial and error. Just try IRQ2
;and do a raelly small DMA (2bytes) and
;wait a see if you get an interrupt.
;If you do that's great, if you don't
;then try the next one. Or just hook
;all the IRQ's, do the DMA, and see
;which one responds.
OldDSPVect dd ? ;The is for the old interrupt vector.
DSPIRQHandler proc far ;This is the routine you want to
;replace it with.
mov al,[SBIRQ] ;Get Old interupt vector.
add al,8
mov ah,35h
int 21h
mov [word ptr OldDSPVect],bx
mov [word ptr OldDSPVect],es
push ds ;Hook the DSP int vector.
mov al,[SBIRQ]
add al,8
mov dx,seg DSPIRQHandler
mov ds,dx
mov dx,offset DSPIRQHandler
mov ah,25h
int 21h
pop ds
3. Enable the DSP interrupt on the PIC (Programmable Interrupt
Controller). Here's how:
OriginalPIC db ?
mov cl,[SBIRQ]
mov ah,1
shl ah,cl
not ah
in al,21h
mov [OriginalPIC],al
and al,ah
out 21h,al
4. Set the sampling rate and voice volumes. I think we'll skip the
voice volumes part. I told you how to calculate the sampling rate value
and how to write to the DSP in an earlier message so go look that up.
But this is how to set the sampling rate (DSPWrite should write AL to
the DSP. The rate I used 196 is not exactly 16K, I think it is a little
faster, but it seems to match the speed of other MOD players better):
mov al,0D1h ;This just turns on the speaker.
call DSPWrite ;(I always forget to do this!)
mov al,40h
call DSPWrite
mov al,196
call DSPWrite
The only difference to this is if you want to play stereo on the SBPro.
If you do you will need to double the sampling rate and turn on the
stero bit on the mixer chip (however, if you have an SBPro you should
use auto-init mode):
mov ax,DMABufferSize ;DSP size if length - 1.
dec ax
call DSPWrite ;Send DSP LS 8 bits of length.
mov al,ah
call DSPWrite ;Send DSP MS 8 bits of length.
Auto-Init Mode:
This is by far the most superior mode on the SB card. (It will not work
on the original SB1.5, but it works great on all of the other cards.)
It is almost exactly like single cycle mode except it will automatically
start the next DMA for you. That means that there is never a break in
the sound. No hiss, clicks, or pops. This is how it works.
You allocate a DMA buffer in memory. Let's say it is 8K. You set up the
DMA to transfer the 8K buffer and to restart at the beginning of the 8K
buffer when it is finished. Basically, to the DMA this is just an
infinite buffer which wraps on itself at 8K.
Next you program the DSP to transfer a 4K buffer in auto-init mode.
(What? 4K you say?) Yep, a 4K buffer. This is what happens. You pre-
load the 8K buffer with data, and then start the DMA. When you get the
first interrupt from the SB card that means the the first 4K of the DMA
buffer has been played and the DSP is now playing the second 4K. You
really don't have to do anything with this interrupt except ack it.
Everything happens by itself and the DSP will already be playing the
second 4k. However, unless you want to hear the same thing over and over
again, you should fill up the first 4K with new data. You don't have to
do it in the interrupt routine. You can set a flag an fill the first 4k
latter if you choose. Just make sure you get it filled before the second
4k runs out.
Now, you will get the second interrupt. This means that the second 4k of
the DMA buffer has been played and the DSP is now playing the first 4K
again. Now all you need to do is to refill the second 4k of the DMA
buffer with more data. This process of alternating buffers goes on
until you either tell the DSP to do a single-cycle transfer or stop the
transfer with a DSP DAh command. That is all there is to it. Here are
the differences between single-cycle mode and auto-init mode:
1. Instead of moving a 49h to port 0Bh move a 59h.
(I think this tells the DMA to loop the buffer?)
2. Instead of using the DSP 14h command use this:
mov al,48h
call DSPWrite
mov ax,DMABufferSize
dec ax
call DSPWrite
mov al,ah
call DSPWrite
mov al,1Ch
call DSPWrite
There you have it. Hopefully this is enough information to get you
going programming the SB cards. If you try it and can't get it to work,
post me here and I'll try to help you. I always respond to my mail (even
if it is to tell you I don't have a clue), so if you don't here from me
you're message probably got eaten by Planet Connect and you'll need to
send it again.
Good Luck!
* Origin: Darc Software Tijeras, NM, USA 1-505-281-6289 (1:301/40)