Welcome to TFM's WAVE SB Page.
TFM's Sound Blaster PAGE
PIO
DMA
DOUBLE BUFFERED DMA
DIGITAL SAMPLING
To create a sound by it's wave you must supply the amplitude setting on every
part of the wave. The samples (specific amplitude values) must be taken
frequently enough to allow an accurate picture of the wave.
These samples of the position of the wave allow the wave to be re-created.
To get an idear of the rates, CD quality is about 44kHz (44,000 samples/sec)
and telephone quality is about 4kHz.
The SB uses two different methods for creating sound.
(i) DMA (Dirrect memory access)
(ii) PIO (Programable I/O)
PIO
Writting to the DSP
Playing digital samples uses the Sound Blasters DSP (Digital Signal Processor.)
The method to do this is:-
1) Read the DSP's WRITE BUFFER STATUS port (2xCh) until bit 7 = 0
2) Write the value to the WRITE COMMAND/DATA port (2xCh)
The x in port addresses means that it depends on the base port (set by jumpers)
e.g. if the SB is at 220h then 2xA = 22A
Dirrect access to the DSP's DAC is achieved by using command 10h this means
that to output a byte to the DAC you first send 10 to the DSP, and then send
the value of the sample to the DSP.
It is the controling program's job to ensure that the samples are sent at
regular intervals. The timing can be achieved by using the PIT (Programable
Interrupt Timer)
DMA
The DMA chip of the PC works in parrallel with the CPU, it's jobs are:-
(i) To move data from memory to hardware
(ii) To move data from hardware to memory
(iii) To move data from hardware to other hardware
(iv) To move data from memory to other memory areas (Not supported by many systems)
The sound blaster can use a DMA channel to move the waveform data from memory
into it's DAC (playback through DMA), or from it's ADC to memory (recording
through the DMA.)
Setting up to play through the DMA is not very difficult. You simply have to
load the correct values into the ports on the DMA chip, and on the SB. The
method to do this is:-
(i) Load the wave data into memory.
(ii) Set up the interupt handler.
(iii) Set up the DMA chip.
(iv) Turn on the speaker (command D1).
(v) Setting up the sampling rate.
(vi) Set up the Transfer to the SB.
Load the wave data into memory
The block must not cross a page
boundary, becasue the DMA chip is unable to cross a page boundary (this
means that the segment for the start and end blocks must be the same) The
block must also be located in the first megabyte of memory. This also
limmits transfers to 64K, if you need more use multiple transfers.
Set up the interrupt handler
When the block has all been played the sound blaster causes an interrupt.
The interrupt generated depends upon the jumper settings on the card (IRQ)
IRQ Interrupt number
2 0Ah
3 0Bh
5 0Dh
7 0Fh
The interrupt must be hooked (either by dirrect manipulation of the interrupt
vector table) or by using DOS to change them (most High levels languages
have functions to do this (except BASIC!!))
When an interrupt vector points to your code, your code will be called every
time the interrupt is called, you MUST PRESERVE ALL REGISTERS and return with
an IRET instruction (high level languages normaly do this if you define the
function as an interrupt handling function.)
The interrupt must
1) Acknowledge the DSP interrupt by reading the DATA AVAILABLE port (2xEh)
once.
2) If there are more blocks to transfer then set them up
3) Output value 20h (EOI) to the interrupt controller port 20h
Don't forget to restore the interrupt vector when you quit the program
Set up the DMA chip
1) Calculate the 20 bit address of the memory buffer you are using
where Base Address = Segment * 16 + Offset
eg 1234h:5678h = 179B8h
2) Send the value 05h to port 0Ah (mask off channel 1)
3) Send the value 00h to port 0Ch (clear the internal DMA flip/flop)
4) Send the value 49h to port 0Bh (for playback) or 45h to port 0Bh (for recording)
5) Write the LSB (bits 0 -> 7) of the 20 bit memory address to port 02h
6) Write the MSB (bits 8 -> 15) of the 20 bit memory address to ort 02h
7) Write the Page (bits 16 -> 19) of the 20 bit memory address to port 83h
8) Send the LSB of DATA_LENGTH to port 03h
9) Send the MSB of DATA_LENGTH to port 03h
10) Send the value 01h to port 0Ah (enable channel 1)
Setting up the sampling rate
First send 040 to the DSP then
the LSB of time constant, then MSB of the time constant
where the time constant = 256 - 1000000 / frequency
Set up the Transfer to the SB
Send the format of the data to the DSP
14h 8 bit
74h 4 bit ADPCM (max 12 KHz)
75h 4 bit ADPCM with reference byte (max 12 KHz)
76h 2.6 bit ADPCM (max 13 KHz)
77h 2.6 bit ADPCM with reference byte (max 13 KHz)
16h 2 bit ADPCM (max 11 KHz)
17h 2 bit ADPCM with reference byte (max 11 KHz)
Other DSP commands
Then send the number of bytes to transfer -1 to the SB. It is a two byte
word, sent with the LSB first.
DOUBLE BUFFERED DMA
The above system is fine for small blocks of data, but when you want to
play a continus digital waveform, which is larger than 64K you have problems
at a sampling rate of 44KHz, this will only last about 2 seconds. The above
method of loading the new data when the interrupt occurs does overcome this,
but however fast you are you will always have a click when the DSP runns out
of data.
The answer is to use double buffered DMA.
(i) Set up a block of memory which doesn't cross a page
boundary (must be an even size as well.) Load the data
(ii) Hook the interrupt
(iii) Set up the DMA chip to send this to the SB, using the
full buffer size
(iv) Set the SB up to recieve a block with half the number
of samples -1 (command 048)
(v) Turn on the speaker (command D1)
(vi) Set the SB up to use auto-initialize DMA (command 01C)
(see other DSP commands)
When an interrupt occurs
(i) There is no need to re-program the SB it simply starts
again at the begining of the buffer.
(ii) Load the half of the buffer which has just been played
with new data.
(iii) Acknowledge the DSP interrupt by reading the DATA
AVAILABLE port (2xEh) once.
(iv) Output value 20h (EOI) to the interrupt controller port 20h
At the end
(i) Disable the speaker (command D3)
(ii) Halt DMA transfer (command D0)
(iii) End auto-initialize mode (command DA)
(iv) Unhook the interrupt
Basically this pseudo code implements a pair of continuos buffers, when the
SB reaches the end of the first buffer it causes an interrupt, it then
auto-initializes itself and reads the same number of bytes from the DMA chip
again.
The DMA chip sees the situation a little differently it sends the bytes from
the buffer. It never knows that the SB reaches the end of the semi-buffer. It
simply sends data whenever it is requested. When it reaches the end of it's
buffer it wraps around to the start.
If no new data was ever loaded the situation would just lead to a repeating
piece of sound, but because you get an interrupt every time half of the buffer
is played, you are able to re-load the other half of the buffer.