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



[Image] Go back to frequently asked questions.



[Image] Go back to the BASIC Archives home page.

