Graphics by Lord Azathoth

Sound Blaster Programming
The Sound Blaster is the standard sound card for the PC. With this card we can add music, sound effects and voice to games. For now I know only how to play a single sample at time, but I'm looking for documentations about sound mixing...
Detect the Sound Blaster DSP I/O base address
The Sound Blaster have a chip, called DSP, dedicated to voice playing. We can program this chip to play WAV, VOC and this sort of sound files. To program the DSP we must use the I/O port of the Sound Blaster. The base address is detected by trying to reset the DSP from port 0x210 to 0x260. The I/O base address is the address of the port where the test was successfull. To reset the DSP we must do 5 things:

1. Write a 1 to the SoundBlaster RESET port (2x6h)
2. Wait for 3 micro-seconds (or read six times the register port)
3. Write a 0 to the SoundBlaster RESET port (2x6h)
4. Read the byte from the DATA AVAILABLE (2xEh) port until bit 7 = 1
5. Poll for a ready byte (AAh) from the READ DATA port (2xAh). Before reading the READ DATA port it is avdvisable.

And there are the code to detect the DSP base address (from the demo program):

    // Resets the SB DSP
    // 'base_address'(in): the base address of the SB I/O ports
    // 'return'(out): result code
    BOOL sb_dsp_reset(WORD base_address)
    {
       int	i;
       int	start_time;

       // Write a 1 to the SB RESET port
       outportb(base_address + SB_DSP_RESET, 1);

       // Wait for 3.3ms
       for(i = 0; i < 6; i++)
       	inportb(base_address + SB_DSP_BUFFER_STATUS);

       // Write a 0 to the SB RESET port
       outportb(base_address + SB_DSP_RESET, 0);

       // Read the byte from the DATA_AVAILABLE port until bit 7 = 1
       start_time = clock();
       while( (inportb(base_address + SB_DSP_DATA_AVAILABLE) < 0x80) &&
       	(clock() - start_time < SB_RESET_TIMEOUT) );

    	// Poll for a ready byte (AAh) from the READ DATA port
       start_time = clock();
       while( ((i = inportb(base_address + SB_DSP_READ_DATA)) != 0xAA) &&
          (clock() - start_time < SB_RESET_TIMEOUT) );

       if(i != 0xAA) return(SB_RESET_DSP);
       return(SB_OK);
    }

    // Resets the SB DSP
    // 'base_address'(out): the base address of the SB I/O ports if successfull
    // 'return'(out): result code
    BOOL sb_dsp_detect_base_address(WORD *base_address)
    {
    	WORD	address;

       for(address = 0x210; address < 0x290; address += 0x010)
       	if(!sb_dsp_reset(address))
          {
          	*base_address = address;
             dsp_base_address = address;
             return(SB_OK);
          }

       return(SB_DETECT_DSP);
    }

Detect the IRQ number
For playing a sample we'll use the DMA to trasfer the data from the main memory to the SB card. When all the block of data is transferred to the SB, the DSP will issue an IRQ (in the range 2, 3, 5, 7, 10). We can detect this IRQ by installing a interrupt handler on all the IRQ and using an (undocumented) command that force the DSP to issue an IRQ. This is the simplest way to autodetect the IRQ number (from the demo program):

    // Detects the SB IRQ number
    // 'irq_number'(out): the IRQ number if successfull
    // 'return'(out): result code
    BOOL sb_dsp_detect_irq_number(BYTE *irq_number)
    {
    	BOOL result;
       clock_t start_time;

       lock_memory();
       detect_install_handlers();
       dsp_irq_number = detect_irq_number = 0;

       // Trigger the interrupt
       start_time = clock();
       sb_dsp_write(SB_IRQ_TRIGGER);
       while((detect_irq_number == 0) &&
       	(clock() - start_time < SB_IRQ_DETECT_TIMEOUT));
       dsp_irq_number = detect_irq_number;
       detect_remove_handlers();

       // IRQ number -> INT number
    	if(result = sb_interrupt_number(dsp_irq_number, &dsp_interrupt_number)) return(result);
       *irq_number = dsp_irq_number;
       return(SB_OK);
    }

The functions `detect_install_handlers()` and `detect_remove_handlers()` are used to install and remove the handlers of IRQ 2, 3, 5, 7 && 10.
Play samples
The Sound Blaster can play samples (voice) in two modes: direct and DMA. If you use the first mode you will use a lot of processor time, but is the simplest to use. The second mode allow the main processor to do other things until the dedicated processor (the DMA) copies the data to the Sound Blaster. But this mode is limited by the old DMA processor, that can transfer data only from (or to) the real memory area (first MB). Si it requires a buffer in the conventional memory and can transfer only up to 64Kb at time. The only way to play samples longest more than 64Kb is to divide the samples in blocks of 64Kb or less, and the Sound Blaster will help us by issuing an interrupt each time a DMA transfer stops. Another limitation is that the buffer musn't cross the 64Kb page boundries.
The procedure for doing a DMA transfer is:

1. Load the sound data into memory
2. Set the `DSP TIME_CONSTANT` to the sampling rate
3. Set up the DMA chip for the tranfer
4. Write `DMA_TYPE_VALUE` value to the DSP
5. Write `DATA_LENGTH` to the DSP (2 bytes, LSB first) where `DATA_LENGTH` = number of bytes to send - 1
6. Repeat steps 3, 4, 5 to play all the sample

The `DSP_TIME_CONSTANT` sets the frequency of reproduction, where

    	(BYTE)TIME_CONSTANT = 256 - 1000000 / frequency

This method allow a sampling rate of max 22222 Hz (Normal Mode). With SB 2.0 or above, we can have a higher frequency, using High Speed mode. The `TIME_COSTANT` will be computed in this way:

    	(WORD)TIME_CONSTANT = 65535 - 256000000 / frequency

The result is a WORD, but we'll send only the MSB to the DSP. The code of the function to set the sampling rate will be:

    // Sets the Time Costant
    // 'frequency'(in): the sampling rate
    // 'return'(out): result code
    PRIVATE	BOOL	driver_set_time_costant(WORD	frequency)
    {
       if((frequency < driver_capability.min_mono_8) ||
          (frequency > driver_capability.max_mono_8)) return(SB_SAMPLE_RATE);

       if(frequency > 23 * 1024)
       {
          WORD time_costant;

          time_costant = 65536 - 256000000 / frequency;
          sb_dsp_write(SB_TIME_COSTANT);
          sb_dsp_write(time_costant >> 8);
          driver_use_high_speed = TRUE;
       } else {
          BYTE time_costant;

          time_costant = 256 - 1000000 / frequency;
          sb_dsp_write(SB_TIME_COSTANT);
          sb_dsp_write(time_costant);
          driver_use_high_speed = FALSE;
       }
       return(SB_OK);
    }

We can program the DMA chip in this way (assuming that the Sound Blaster use DMA channel 1):

1. Calculate the 20 bit address of the memory buffer you are using where Base Address = Segment * 16 + Offset
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)

But with Sound Blaster 2.0 or better, is possible to use an Auto Initialized DMA mode. The difference is that both DMA controller and DSP will automagically reinitialize at the end of the buffer transfer. This permit to avoid the presence of a noise (a 'click'), produced by the sound card at the end of the transfer of a buffer, before starting to transfer the new.
This is all for now. I will write more when I've the time. Remeber to see the demo program that play WAV files (but only 8 bits, mono not compressed).

[main] [prec] [next] [email]
Site Made and Maintained by The Dark Angel <DarkAngel@tin.it>
My ICQ Number is 2063026
You Visited This Page
times
This page hosted by Get your own Free Home Page