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

==========
BXh 16-bit DMA mode

Output : bCommand, bMode, wLength.LowByte, wLength.HighByte
Remarks: bCommand is as follows:
        D0 - 0
        D1 - Fifo: 0-off, 1-on
        D2 - Auto-Init: 0-Single-Cycle, 1-Auto-Init
        D3 - A/D: 0=output, 1-input
        D4 - 1
        D5 - 1
        D6 - 0
        D7 - 1

        bMode is as follows:
        D0 - 0
        D1 - 0
        D2 - 0
        D3 - 0
        D4 - Signed: 0-unsigned, 1-signed
        D5 - Stereo: 0-mono, 1-stereo
        D6 - 0
        D7 - 0

        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

==========
D5h Pause 16-bit DMA

Output : D5h
Remarks: Pause 16-bit DMAs.
Cards  : SB16

==========
D6h Continue 16-bit DMA

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.

GoodReset:            ;Found an SB card!

;-------------------------
; SB DSP Write/Read/Ack

This is how to write to the SB DSP.

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  al,40h
 call DSPWrite
 mov  al,226
 call DSPWrite

 mov  al,0eh
 call MixerRead
 mov  ah,al
 or   ah,22h
 mov  al,0eh
 call MixerWrite


MixerRead reads from the address in AL on the mixer chip.

 mov  dx,[SBADD]  ;Load in address for the Mixer
 add  dx,4        ;selection register.

 out  dx,al       ;Select the Mixer register.

 mov  cx,6        ;Wait for the chip.
MixLoop:
 in   al,dx
 loop MixLoop

 inc  dx          ;Read the data out of the data register.
 in   al,dx


MixerWrite will write AH to register AL of the Mixer chip.

 mov  dx,[SBADD]  ;Load in address for the Mixer
 add  dx,4        ;selection register.

 out  dx,al       ;Select the Mixer register.

 mov  cx,6        ;Wait for the chip.
MixLoop:
 in   al,dx
 loop MixLoop

 inc  dx          ;Send the data to the data register.
 mov  al,ah
 out  dx,al
 dec  dx

 mov  cx,35       ;Wait for the chip.
MixLoop2:
 in   al,dx
 loop MixLoop2


5. Fill the DMA buffer.  Just write your data into segment DMABuff.
(DMABuff:0000)  I think you can figure this out for yourself.

6. Program the Transfer.  I'll just give you the code.  It is hard coded
for DMA channel #1.

 mov  al,5          ;Mask off channel 1
 out  0ah,al

 xor  al,al         ;Clear the byte pointer.
 out  0ch,al

 mov  al,49h        ;Set for single-cycle transfer
 out  0bh,al        ;to DSP DAC.

 mov  ax,[DSPBuff]  ;Tell the DMA where the data is.
 mov  cl,4

 mov  dl,ah         ;Compute the MS 4 bits of linear
 xor  dh,dh         ;address.
 shr  dl,cl

 shl  ax,cl         ;Compute the LS 16 bits.

 out  02h,al        ;Send LS 8 bits of linear address.
 mov  al,ah
 out  02h,al        ;Send next 8 bits of linear address.
 mov  al,dl
 out  83h,al        ;Send MS 4 bits.

 mov  ax,DMABufferSize  ;DMA length is defined as
 dec  ax                ;actual length - 1.

 out  03h,al        ;Send LS 8 bits of length.
 mov  al,ah
 out  03h,al        ;Send MS 8 bits of length.

 mov  al,1          ;Turn back on DMA channel 1.
 out  0ah,al

 mov  al,14h        ;Send DSP command for 8-bit mono
 call DSPWrite      ;single-cycle DMA.

 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)