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