Using a sound card in QBasic

   * Introduction
   * Sampling Sound
   * Recording and playing back sound
   * Playing WAV files
   * FM synthesized music
   * Summary

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
[Image]
---------------------------------------------------------------------------
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
 NumberCommand           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
 NumberFunction
 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
 ChannelOffset 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 43  2  1  0
 ParameterAM 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 43 2 1 0
 ParameterAM 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
 ParameterScaling 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
[Image]
---------------------------------------------------------------------------
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.
[Image]
---------------------------------------------------------------------------
The attack rate and decay rate are specified by register 60h:
 Bit      7  6  5  4  3  2 1  0
 ParameterAttack 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
 ParameterSustain 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
 NoteNumber
 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
 ParameterEight LSB of note number
while register B0h looks like this:
 Bit      7   6  5      4 3 2  1  0
 ParameterUnused 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
[Image]
---------------------------------------------------------------------------
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

Back home, please! Too much information!
