Using a sound card in QBasic

Introduction

Soon after the introduction of the Personal Computer, people began to realize its possibilities for playing games. The PC was from itself equipped with a relatively adequate graphics system, but the sound capabilities were very limited. This changed with the introduction of special add-on sound cards, such as the AdLib card and the SoundBlaster card from Creative Labs. Nowadays, the majority of PCs has a sound card installed, and most of them are more or less compatible with the SoundBlaster card.

In this document, we will look at using a SoundBlaster compatible card with QBasic. Such a card actually combines different methods or systems for producing sound. These methods may include waveform sound, FM systhesizer music, sound from the Creative Music System (not used a lot nowadays), MIDI compatibility and wave table sounds. Also, many sound cards are capable of digitizing sound. In this document, we will limit ourselves to discussing waveform and FM sound, because these two methods are the most widespread. With the experience gained from working with these methods and the specifications of the system, you will be able to work out how to control other sound systems.

Controlling a sound card in QBasic is done by sending data to hardware ports, by means of the OUT statement. Hardware ports are the connections between the computer and peripheral devices, such as printers or modems. Data can also be send the other way, from the device to the computer. In QBasic, this data is read in using the INP function.

The SoundBlaster card can be configured to use different sets of hardware ports, identified by their base address, which is the number of the first port in the set. For instance, if the card is set to use hardware port numbers 220h to 233h, then its base address is 220h. To control the sound card, you have to find out its base address. Usually, the install program that comes with the sound card, has set up an environment variable containing the base address. If you type the command


SET

at the DOS command line, a list will be printed with all the environment variables. There should be an item in the list that looks like this:


BLASTER=A220 I5 D1 T3

The number just after the 'A' is the base address, in hexadecimal notation, in this case 220h. The other numbers specify the interrupt request number, the DMA channel number and the version number, but these don't concern us. The programs in this document assume 220h to be the base address, so if your card is set to another address, you will have to change the value BaseAddr in the programs.

To save the programs discussed in this document to your hard disk in standard ASCII format, click on the name with the right-hand mouse button and select 'Save Link As...' from the menu.

Sampling sound

First, we will discuss sampling sound, or electronical sound recording. To do this, you need to connect a sound source, such as a microphone or a cassette player, to the MIC or the Line-In connector of the sound card. The SoundBlaster card is equipped with an analogue-to-digital convertor, ADC for short. This is an electronical circuit that transforms the voltage on the connector to a numerical value which can be stored in a computer's memory. In this section, we will look at 8-bits mono sound sampling, because this works on all cards. This means that one sample of sound is one byte long.

There are two ways of sampling sound: through the processor or through DMA (Direct Memory Access). DMA means that the sound data goes directly into the computer's memory, without intervention of the processor. DMA is faster, but fairly difficult, if not impossible, to accomplish in QBasic. Here, we will look at retrieving one byte of sound data at a time through the processor.

To let the sound card take a sample of the sound, we have to send the right command number to the command port. For DSP (Digital Signal Processing, what we are doing now), the number of the command port is base+Ch. Command numbers include 10h, which means 'Output a value to the speakers' and 20h, meaning 'Read a value from the microphone'. This value can then be read from the data port, which has number base+Ah. There are many other command numbers, but these can be found in more specific literature. To see how to use command 20h, we will have a look at program 1, which plots the sampled data on the screen.


Program 1: SAMPLE.BAS

CONST ScreenMode = 12, xMax = 640 'Change for other screen modes
CONST BaseAddr = &H220 'Change if your sound card uses another base address

CONST CommAddr = BaseAddr + &HC, DataAddr = BaseAddr + &HA

DEFINT A-Z
DIM Byte(xMax)
SCREEN ScreenMode
DO
  OUT CommAddr, &H20 'Give command to sample a byte
  PRESET (i, Byte(i))
  Byte(i) = INP(DataAddr) 'Read value from data port
  PSET (i, Byte(i))
  i = (i + 1) MOD xMax 'Wrap i when end is reached
LOOP

First, some useful constants are defined. If your hardware doesn't support screen mode 12, change it to another mode. xMax is the maximum x-coordinate, which may have to change as well. An array is defined to hold the value for each x-coordinate on the screen. In the loop that follows, the command 20h is send to the command port. Then, the sample value is read in from the data port and assigned to an element of the array Byte(). This value is then plotted on the screen. The PRESET statement clears the points of the previous plot. The variable i is incremented each time until it reaches xMax, and then it is set back to zero. Figure 1 shows what you may see when you run SAMPLE.BAS and talk into the microphone.


Figure 1: The output of the program SAMPLE.BAS


With this program, you could use your PC as an oscilloscope. However, you can't measure voltages with it, because the SoundBlaster uses a technique called Automatic Gain Control (AGC), which automatically adjusts the recording level according to the level of the sound source. This means that (between certain boundaries) different levels of sound volume will produce the same level on the screen.

Recording and playing back sound

By now, we have sampled sound and stored it in an array. The next step is playing the sound back again to the digital-to-anolog convertor (DAC). We will read in as much values as we can hold. Then we will output these values back to the sound card again, and hear the sound we have just recorded. The program RECPLAY.BAS is an implementation of this concept.


Program 2: RECPLAY.BAS

DECLARE SUB ResetSB ()
DECLARE SUB Record ()
DECLARE SUB PlayBack ()
CONST NoOfSamples = 32766 'Maximum array length
CONST BaseAddr = &H220 'Change if your sound card uses another base address

CONST CommAddr = BaseAddr + &HC, DataAddr = BaseAddr + &HA
CONST ResetAddr = BaseAddr + &H6

DEFINT A-Z
DIM SHARED Byte(NoOfSamples)

DO
  CLS
  PRINT "1. Record sound"
  PRINT "2. Play back sound"
  PRINT "3. Quit"
  DO
    Choice$ = INPUT$(1)
  LOOP WHILE INSTR("123", Choice$) = 0 'Check for valid choice
  SELECT CASE Choice$
  CASE "1"
    Record
  CASE "2"
    PlayBack
  CASE "3"
    CLS
    END
  END SELECT
LOOP

SUB Record
  CLS
  PRINT "Recording..."
  LOCATE 3, 1
  PRINT STRING$(NoOfSamples / 500, "±"); 'Print bar
  LOCATE 3, 1
  ResetSB
  time! = TIMER
  FOR i = 0 TO NoOfSamples
    IF i MOD 500 = 0 THEN PRINT "Û"; 'Fill up bar
    OUT CommAddr, &H20 'Give command to sample a byte
    Byte(i) = INP(DataAddr) 'Read value from data port
  NEXT i
  time! = TIMER - time!
  LOCATE 5, 1
  PRINT "Sampling rate:"; NoOfSamples / time!; "Hz."
  PRINT "Press any key to continue."
  key$ = INPUT$(1)
END SUB

SUB PlayBack
  CLS
  PRINT "Playing back..."
  LOCATE 3, 1
  PRINT STRING$(NoOfSamples / 500, "±"); 'Print bar
  LOCATE 3, 1
  ResetSB
  OUT CommAddr, &HD1 'Turn speaker on
  time! = TIMER
  FOR i = 0 TO NoOfSamples
    IF i MOD 500 = 0 THEN PRINT "Û"; 'Fill up bar
    OUT CommAddr, &H10 'Give command to output a byte
    OUT CommAddr, Byte(i) 'Output value
  NEXT i
  time! = TIMER - time!
  OUT CommAddr, &HD3 'Turn speaker off
  LOCATE 5, 1
  PRINT "Play back rate:"; NoOfSamples / time!; "Hz."
  PRINT "Press any key to continue."
  key$ = INPUT$(1)
END SUB

SUB ResetSB
  OUT ResetAddr, 1
  OUT ResetAddr, 0
END SUB

At the start of the program, a new constant is defined: the reset port, base+6h. The DSP chip is reset by sending the value 1 to this port, followed by the value 0. This is done in the SUB ResetSB. In the main module of the program, an array Byte() is defined with 32767 elements, which is the largest array QBasic can handle. Then, a menu is printed with three choices.

The first choice leads to the SUB Record. In this SUB, an empty bar is printed, which is filled up with one block for each 500 samples taken. This indicates the progress of the recording. After resetting the DSP chip, the array Byte() is filled with sound samples in the same way as in program 1. When the array is full, the sampling rate is calculated by deviding the number of samples taken by the time it took to take these samples. The sampling rate is a measure for the quality of the recorded sound. After waiting for a keypress, the program returns to the menu.

The second choice starts the SUB PlayBack, which is very similar to the SUB Record. After resetting the DSP chip, the speaker is switched on by sending the command number D1h to the command port. Then the command to output a byte to the speaker (10h) is send to the command port, followed by the byte in question. After all bytes are sent, the speaker is switched off again, by means of the command D3h.

When you run this program, you can record a few seconds of sound, and play it back again. If you happen to own a very fast computer, the record time may be very short. In that case, you could insert some kind of delay loop in the program, having samples taken less often. Of course, increasing the record time brings down the sampling rate (since the number of samples is fixed) and thus the sound quality.

Also, when running this program on a very fast computer, you may run into problems. The DSP chip needs time to process the commands sent to it. When command are sent too short after one another, things may go wrong. To avoid this, you have to check if the DSP chip is ready to receive a new command. This can be done by reading a byte from the command port. If bit 7 of this byte is clear, i.e. zero, the DSP chip is ready to receive a command. So if you run into problems using RECPLAY.BAS, insert the following lines before each OUT statement that writes to the command port:

DO
LOOP WHILE (INP(CommAddr) AND 128) = 0

This pauses the program for as long as the DSP chip is processing other commands. On slower computers, this loop isn't necessary, and would only bring down the sampling rate.

Playing WAV files

You could expand RECPLAY.BAS by adding possibilities for saving the recorded sound to disk and loading other sounds. You also might want to play sounds recorded with another program, for instance in the WAV format. This is the format used by Microsoft Windows to store sampled sounds in. It contains a header of 44 bytes, followed by the wave data. In this header, bytes 25-28 and bytes 29-32 both contain the sampling rate of the sound. Bytes 41-44 contain the number of sound samples. The following program WAVE.BAS plays an 8 bit mono WAV file. The program is quite simple, not taking into account the original sampling rate. Because of QBasic array size limits, it can only play the first 32766 bytes of the file. However, the program serves as a good example for playing sound from a file.


Program 3: WAVE.BAS

DECLARE SUB ResetSB ()
DECLARE SUB PlayWav (FileName$)
CONST BaseAddr = &H220 'Change if your sound card uses another base address

CONST CommAddr = BaseAddr + &HC, ResetAddr = BaseAddr + &H6

DEFINT A-Z

LINE INPUT "Enter file name: "; FileName$
PlayWav FileName$
END

SUB PlayWav (FileName$)
  PRINT "Loading file..."
  OPEN FileName$ FOR BINARY AS #1
  dummy$ = INPUT$(40, #1) 'Discard first 40 bytes
  length& = CVL(INPUT$(4, #1)) 'Next 4 bytes is length (4 bytes = LONG)
  IF length& > 32766 THEN 'Only WAVs shorter than 32767 bytes can be played
    PRINT "Lenght of file exceeds maximum array length."
    PRINT "Only the first 32766 bytes will be played."
    length& = 32766
  END IF
  length = length& 'Convert to integer for more speed
  DIM Byte(1 TO length)
  FOR i = 1 TO length
    Byte(i) = ASC(INPUT$(1, #1)) 'Read a byte in
  NEXT i
  CLOSE #1
 
  PRINT "Playing back..."
  ResetSB
  OUT CommAddr, &HD1 'Turn speaker on
  FOR i = 1 TO length
    OUT CommAddr, &H10 'Give command to output a byte
    OUT CommAddr, Byte(i) 'Output value
  NEXT i
  OUT CommAddr, &HD3 'Turn speaker off
END SUB

SUB ResetSB
  OUT ResetAddr, 1
  OUT ResetAddr, 0
END SUB

The main module only asks for the file name to play. Control is then passed to the SUB PlayWav. This subprogram is devided into two parts: the loading and the playing of the file. The file is first loaded into an array for more speed. Of the header, the first 40 bytes are discarded. We are only interested in the length of the data, bytes 41-44. These four bytes are read in as a string and converted to a variable of data type LONG with the CVL function. If the length exceeds 32766 bytes, a message is printed and the length is set to 32766. Then, the data is read in byte by byte into the array. This array is then played in the same way as in RECPLAY.BAS.

This program should give you an idea of how to play data from disk. You could add a delay loop in the program so that the play back rate corresponds to the sampling rate. You could also let the file be played back backwards, to discover those hidden messages on the new Beatles record.

To conclude this section, we will give an overview of the DSP command numbers we used. There are a lot more than we give here; these are mainly concerned with DMA DSP. Information about this can be found in more specific literature.

Table 1: Some DSP commands numbers

Number Command Remarks
10h Direct DAC, 8 bit Send byte directly after command
20h Direct ADC, 8 bit Sampled byte can be read from port address base+Ah
1Dh Enable speaker
3Dh Disable speaker

FM synthesized music

A very different form of sound output is FM systhesis. This section applies also to AdLib sound cards, but AdLib owners should change the base address in the programs below to 380h. In this section we will look at how to produce sound using the FM system, and we will experiment with some of the parameters used to define the sound.

FM stands for frequency modulation. The sound is formed by having a carrier sound being modulated by a modulator sound. We can define up to nine 'instruments', each consisting of a carrier and a modulator. We can let these nine instruments play different notes together, producing complicated tunes. The instruments are defined by a lot of parameters, from which we will discuss only a few.

The FM chip on the sound card is programmed by setting registers in the chip to certain values. There are 224 of such registers, so you'll understand that we won't discuss every one of those. To set a register to a value, we send the number of the register to the Register Port, whose address is base+8. Then, we send the desired value to the Data Port, with address base+9. The carrier and modulator of each instrument both have four registers in which parameters are placed. Since there are nine channels (instruments), that already gives us 2 x 4 x 9 = 72 registers to program! Of course, we don't have to use all nine channels. The register numbers for the carrier of channel 1 are given in table 2.

Table 2: FM register numbers for carrier of channel 1

Number Function
20h Amplitude modulation/Vibrato/EG type/Key scaling/Octave shift
40h Key scaling level/Output level
60h Attack rate/Decay rate
80h Sustain level/Release time

The functions given in table 2 will be explained below. To find the other 68 register numbers, add the offset numbers from table 3 to the base numbers in table 2.

Table 3: FM register offset numbers for carrier and modulator of channels 1-9

Channel Offset for carrier Offset for modulator
1 00h 03h
2 01h 04h
3 02h 05h
4 08h 0Bh
5 09h 0Ch
6 0Ah 0Dh
7 10h 13h
8 11h 14h
9 12h 15h

For example, to find the register number for the Attack rate/Sustain rate of the modulator of channel 6, add 0Dh to 60h to find 6Dh.

To define an instrument, values should be assigned to parameters. As you can see in table 2, two or more parameters are combined into one register. Each register is eight bits wide, so the register values can range from 0 to 255. These eight bits are devided over two or more parameters, so each parameter has less than eight bits available. For instance, if a parameter has three bits available, its values will range from 0 to 7. The total register value is found by combining the values for the different parameters using the appropiate coefficients.

We will now look at what the parameters mean. We will look at the registers for the carrier of channel 1, i.e. 20h, 40h, 60h and 80h, but the same goes of course for the other channels.

Register 20h looks like this:

Bit 7 6 5 4 3 2 1 0
Parameter AM Vib     Octave shift

The value (ranging from 0 to 15) in bits 0-3 specifies whether the octave a note is played at should be changed from the specified value. If set to 0, the note is played one octave lower than specified. If set to 1, there is no change. If set to 2, the note is shifted one octave up. There are other values possible, but we will not discuss those here. We will also not discuss the function of bits 4 and 5. Setting bit 6 applies vibrato to the sound. Setting bit 7 causes amplitude modulation in the sound. The depth of the amplidute modulation and vibrato is specified for all channels through one register, BDh:

Bit 7 6 5 4 3 2 1 0
Parameter AM Vib            

If bit 6 is set, the vibrato (if applied) is set to 14 percent. If clear, the vibrato is 7 percent. If bit 7 is set, the amplitude modulation depth (if applied) is 4.8 dB. If clear, it is 1 dB. We will leave the other bits of register BDh for what they are.

Register 40h looks like this:

Bit 7 6 5 4 3 2 1 0
Parameter Scaling Output level

The output level value, bits 0-5, ranges from 0 to 63. 63 corresponds to the lowest level, 0 dB, and 0 to the highest level, 47 dB. Bits 6-7 (ranging 0-3) specify how quickly the output level rises if the pitch of the sound goes up. 0 is no rise, 1 is 1.5 dB/octave, 2 is 3 dB/octave and 3 is 6 dB/octave.

Before we look at registers 60h and 80h, we first need to know a little bit more about how a note played on an instrument is built up. We distinguish four phases in the note. First, there is the 'attack'. This is the fast rise in level at the beginning of the note. Then follows the 'decay'. This is when, after reaching peak level, the sound volume drops till a certain level. This level is called the 'sustain' volume. The sound stays at this level until the 'release' time is reached, at which point the sound stops. Look at figure 2 to see a graphical representation of this idea.


Figure 2: The different phases in a note


Now, the attack rate defines how quickly the sound level initially rises, and the decay rate specifies how quickly it drops again to the sustain volume. The release time controls how long the sound stays at the sustain volume. These four parameters can be varied producing different sound 'shapes'. A number of these shapes is given in figure 3.


Figure 3: Schematic results of varying attack rate, decay rate, sustain level and release time. a: high attack rate, low decay rate, low sustain level, short release time. b: low attack rate, high decay rate, high sustain level, long release time. c: high attack rate, high decay rate, low sustain level, long release time. d: high attack rate, high decay rate, high sustain level, short release time.


The attack rate and decay rate are specified by register 60h:

Bit 7 6 5 4 3 2 1 0
Parameter Attack rate Decay rate

Bits 4-7 specify the attack rate, a value between 0 (slowest) and 15 (fastest).

The sustain level and release time are controlled by register 80h, which look like this:

Bit 7 6 5 4 3 2 1 0
Parameter Sustain level Release time

Bits 0-3 specify the release time, from 0 (longest) to 15 (slowest). Bits 4- 7 specify the sustain level, from 0 (loudest) to 15 (softest).

As you can see, defining an instrument is not the simplest of tasks. There are still more registers, but I'm sure you've had enough for a while by now.

Now we will have a look at how to actually use the instrument we have just learnt to define. To hear a note play, we have to specify the note and the octave. We have eight octaves at our disposal, numbered 0-7. The notes, normally written down as letters, have gotten numbers. These can be found in table 4:

Table 4: FM note representations

Note Number
C# 16Bh
D 181h
D# 198h
E 1B0h
F 1CAh
F# 1E5h
G 202h
G# 220h
A 241h
A# 263h
B 287h
C 2AEh

As you can see, these numbers take up ten bits, and because we have only eight-bits registers, the numbers have to be split into two parts. The eight least significant bits go into registers A0h (for channel 1) to A8h (for channel 9). The two most significant bits go as bits 0 and 1 into registers B0h (for channel 1) to B8h (for channel 9). Register A0h looks like this:

Bit 7 6 5 4 3 2 1 0
Parameter Eight LSB of note number

while register B0h looks like this:

Bit 7 6 5 4 3 2 1 0
Parameter Unused Switch Octave Two MSB

Bits 2-4 specify the octave the note is played at. Bit 5 turns the channel on and off. When bit 5 is set, the note starts playing. When it is cleared, the sound stops, and a new note can be played. Registers A0h and B0h are for channel 1, but the procedure is of course the same for the other channels (registers A1h-A8h and B1h-B8h).

Now we're ready to play some music. Program 4 shows how to play a simple tune, using channels 1, 2 and 3.


Program 4: FM-TUNE.BAS

DECLARE SUB SetReg (Reg%, Value%)
CONST BaseAddr = &H220 'Change if your sound card uses another base address

CONST RegAddr = BaseAddr + 8, DataAddr = BaseAddr + 9

DEFINT A-Z

FOR i = 0 TO 224
  SetReg i, 0 'Clear all registers
NEXT i
SetReg &H20, &H1 'Plays carrier note at specified octave ch. 1
SetReg &H23, &H1 'Plays modulator note at specified octave ch. 1
SetReg &H40, &H1F 'Set carrier total level to softest ch. 1
SetReg &H43, &H0 'Set modulator level to loudest ch. 1
SetReg &H60, &HE4 'Set carrier attack and decay ch. 1
SetReg &H63, &HE4 'Set modulator attack and decay ch. 1
SetReg &H80, &H9D 'Set carrier sustain and release ch. 1
SetReg &H83, &H9D 'Set modulator sustain and release ch. 1
SetReg &H21, &H1 'Plays carrier note at specified octave ch. 2
SetReg &H24, &H1 'Plays modulator note at specified octave ch. 2
SetReg &H41, &H1F 'Set carrier total level to softest ch. 2
SetReg &H44, &H0 'Set modulator level to loudest ch. 2
SetReg &H61, &HE4 'Set carrier attack and decay ch. 2
SetReg &H64, &HE4 'Set modulator attack and decay ch. 2
SetReg &H81, &H9D 'Set carrier sustain and release ch. 2
SetReg &H84, &H9D 'Set modulator sustain and release ch. 2
SetReg &H22, &H1 'Plays carrier note at specified octave ch. 3
SetReg &H25, &H1 'Plays modulator note at specified octave ch. 3
SetReg &H42, &H1F 'Set carrier total level to softest ch. 3
SetReg &H45, &H0 'Set modulator level to loudest ch. 3
SetReg &H62, &HE4 'Set carrier attack and decay ch. 3
SetReg &H65, &HE4 'Set modulator attack and decay ch. 3
SetReg &H82, &H9D 'Set carrier sustain and release ch. 3
SetReg &H85, &H9D 'Set modulator sustain and release ch. 3

READ NoOfNotes

FOR i = 1 TO NoOfNotes
  time! = TIMER
  FOR j = 0 TO 2 'Voices 0, 1 and 2
    READ octave
    READ note$
    SELECT CASE note$
    CASE "C#"
      SetReg &HA0 + j, &H6B 'Set note number
      SetReg &HB0 + j, &H21 + 4 * octave 'Set octave and turn on voice
    CASE "D"
      SetReg &HA0 + j, &H81
      SetReg &HB0 + j, &H21 + 4 * octave
    CASE "D#"
      SetReg &HA0 + j, &H98
      SetReg &HB0 + j, &H21 + 4 * octave
    CASE "E"
      SetReg &HA0 + j, &HB0
      SetReg &HB0 + j, &H21 + 4 * octave
    CASE "F"
      SetReg &HA0 + j, &HCA
      SetReg &HB0 + j, &H21 + 4 * octave
    CASE "F#"
      SetReg &HA0 + j, &HE5
      SetReg &HB0 + j, &H21 + 4 * octave
    CASE "G"
      SetReg &HA0 + j, &H2
      SetReg &HB0 + j, &H22 + 4 * octave
    CASE "G#"
      SetReg &HA0 + j, &H20
      SetReg &HB0 + j, &H22 + 4 * octave
    CASE "A"
      SetReg &HA0 + j, &H41
      SetReg &HB0 + j, &H22 + 4 * octave
    CASE "A#"
      SetReg &HA0 + j, &H63
      SetReg &HB0 + j, &H22 + 4 * octave
    CASE "B"
      SetReg &HA0 + j, &H87
      SetReg &HB0 + j, &H22 + 4 * octave
    CASE "C"
      SetReg &HA0 + j, &HAE
      SetReg &HB0 + j, &H22 + 4 * octave
    END SELECT
  NEXT j
  READ duration!
  DO
  LOOP WHILE time! + duration! > TIMER 'Wait as long as duration
  FOR j = 0 TO 2
    SetReg &HB0 + j, 0 'Switch voices off
  NEXT j
NEXT i

END

DATA 15: REM Number of notes
'Data below: octave1, note1, octave2, note2, octave3, note3, duration
DATA 4,B,4,G,4,D,.5
DATA 4,B,4,G,4,D,.5
DATA 4,B,4,G,4,D,.5
DATA 4,B,4,G,4,D,.5
DATA 5,D,4,B,4,F#,.25
DATA 4,C,4,A,4,E,.25
DATA 4,C,4,A,4,E,.25
DATA 4,B,4,G,4,D,.25
DATA 4,A,4,E,3,C,1
DATA 4,A,4,F#,4,D,.5
DATA 4,A,4,F#,4,D,.5
DATA 4,B,4,G,4,E,.5
DATA 4,C,4,A,4,F#,.5
DATA 5,D,4,A,4,F#,1
DATA 5,G,5,D,4,B,.5

SUB SetReg (Reg, Value)
  OUT RegAddr, Reg
  OUT DataAddr, Value
END SUB

First, the SUB SetReg is declared. This SUB puts the specified value into the specified register. Then, all registers are cleared. The registers for the first three channels are set to three identical instruments; some kind of electronic piano sound. The octaves and notes are read from the DATA statements, and the SELECT CASE statement chooses the correct number for the note. The octave and note numbers are put in their respective registers, and the note starts playing. We wait for a time specified by the duration variable using the TIMER system variable, and then registers B0h, B1h and B2h are set to zero, and bit 5 with them, to switch the channel off. The DATA statements at the end of the program describe the tune. The first DATA statement specifies the number of notes, and the statements that follow specify the octave and note for each channel, and the duration of the note in seconds.

Working out the correct values for an instrument can be a long and tedious process. However, there are ways of making this easier. For your convenience, I have made the program FM-LAB.BAS. This program lets you play with four of the parameters: the attack rate, the decay rate, the sustain level and the release time. When you run this program, a screen is printed as depicted in figure 4.


Figure 4: The screen of the program FM-LAB.BAS
The parameter currently chosen is highlighted. You can adjust the value for this parameter using the up and down arrow keys. You can choose another parameter with the left and right arrow keys. Press Enter to hear the note you have just defined. Esc ends the program. This program demonstrates very well the effects the different parameters have on the sound. You could expand this program to include the other parameters as well. When you are satisfied with the sound, you could use the values in a program similar to FM-TUNE.BAS.

The program FM-LAB.BAS doesn't introduce new SoundBlaster programming techniques, so we won't have a detailed look at it. We hope that this document has given you some idea on SoundBlaster programming, and has encouraged you to perform some experminents of your own.

Summary

In this document we have looked at how to talk to a SoundBlaster compatible sound card, using OUT and INP statements. We have looked at digitizing sound from an external sound source. We have seen that the sound can be plotted, stored and played back. We have seen how we can read a WAV file and how to play it back. We have looked at a number of FM registers for specifying instruments, notes and octaves. We used this knowledge to program a simple tune using three channels. Finally, we have experimented a bit with four of the parameters: attack rate, decay rate, sustain level and release time.

Wouter Bergmann Tiest