Task 14: Music and Sound Effects  

Contents

The BEEP Statement
The SOUND Statement
The PLAY Statement
The _SNDOPEN statement
The _SNDPLAY and _SNDPLAYCOPY Statements
The _SNDLOOP Statement
The _SNDPLAYFILE Statement
The _SNDPLAYING Statement
The _SNDSTOP Statement
The _SNDVOL Statement
The _SNDPAUSE and _SNDPAUSED Statements
The _SNDLIMIT Statement
The _SNDLEN Statement
The _SNDSETPOS and _SNDGETPOS Statements
The _SNDCLOSE Statement
A Sound Programming Example
Your Turn
Command Reference



Can you imagine playing computer games today that have no sound? Well, the earliest computer games, mainly in the 70's, were just that, completely silent! It wasn't until the advent of the first home video game consoles that sound and video games went hand in hand. These early console sounds were very crude by today's standards, with their beeps and noise generators, but it was fantastic nonetheless. The early home computer market quickly caught up allowing home programmers the ability to create noisy software on demand. Early IBM PC and compatible programmers had nothing more than a simple 4 ohm speaker to play with, affectionately named the "PC Squeaker" by programmers of the day. Other early home computers, such as the Ataris, Apples, Commodores and TRS-80 Color Computers, could synthesize their sounds with up to 3 voices typically through a television speaker. Before long companies such as Creative Labs and AdLib were introducing dedicated sound boards with FM synthesis and MIDI playback capability that was truly mind-blowing at the time. With each new advance in computer sound technology came new computer games that took advantage of their capabilities. Today however sound is ubiquitous with computing with no one giving it a second thought. Sound cards are routinely included on motherboards and built into all portable devices as a standard feature. Only the precision audiophile or extreme gamer even bothers to go out and purchase a high end sound card today. What this means to you, budding game programmer, is that you have very powerful sound processors at your fingertips for your games to come to life with.

Let's start off by introducing the sound statements built into QB64 that are left-overs from the early days of BASIC. These statements typically used the PC Squeaker to create frequency and duration controlled sound waveforms to induce beeps and blips. Some newer computers today do not have a built in PC speaker on the motherboard and therefore these basic sounds may not be heard on your system (you're not missing much). Other motherboard manufacturers have taken this into account and route PC speaker output through the built-in sound card. Windows sometimes gets confused by these early statements as well and will interpret the statement as a Windows warning sound or similar. These statements are being shown so you know they exist and how to use them, however, with QB64's built-in vastly improved sound capability you may never use them.


The BEEP Statement


The BEEP statement is as simple as it gets when generating a sound for your programs. It does literally what the name implies and generates a beep tone. Early mainframe terminals could generate a beep by having an ASCII character 7 (BEL) sent to them. The BEEP command is a throw-back from those early days of computing.

PRINT "I am going to beep when you press ENTER."
SLEEP
BEEP
PRINT "That was cool!"


Note that while the beep is being produced your program will pause for that very slight duration. Using BEEP in a loop for game sounds is therefore not recommended.


The SOUND Statement


The SOUND statement is used to create a sound determined by a given frequency and duration. The SOUND statement does not use your sound card but instead uses the audio oscillator built into your motherboard for generating tones out of the PC speaker. QB64 however reroutes these tones to your computer's sound card. The SOUND statement is used by supplying a frequency and duration like so:

SOUND frequency, duration

Frequency can be any value from 37 to 32767 and duration can be any positive number including zero. Duration is timed in 1/18th second intervals meaning that roughly a duration of 18.2 equals 1 second. If you know how to read music and wish to generate tunes from sheet music using the SOUND statement there is a listing of notes/octaves and their frequency values on the QB64 Wiki SOUND page.

Here is an example of the SOUND statement being used to play "My Bonnie" taken from the QB64 Wiki SOUND page. It has been included in the .\tutorial\task14 directory as MyBonnie.BAS.





The PLAY Statement


The PLAY statement was designed to play a predefined string of notes using built in musical language directives. By default PLAY plays notes from the third octave in quarter notes in 4/4 time:

PLAY "CDEFGAB" ' third octave scale in quarter notes

Here a directive was added to use eighth notes instead:

PLAY "L8CDEFGAB" ' third octave scale in eighth notes

There are music directives to set the tempo, volume, legato, staccato, and add rests amongst a variety of other directives. A complete list of them are contained in the QB64 Wiki PLAY page. Below is the code to play the William Tell Overture done completely with the PLAY statement! Keep in mind that someone had to hand code this entire thing in at some point using sheet music. It has been included in the .\tutorial\task14 directory as WTOPlayDemo.BAS.




As the SOUND and PLAY statements painfully point out BASIC was never meant to handle sound beyond simple beeps and tones generated by the PC speaker sound oscillator. Now that sound cards are standard fare let's get into the new sound statements offered by QB64.


The _SNDOPEN Statement


QB64 makes use of external sound files that are loaded into memory. The _SNDOPEN statement is used to load a sound file into memory and return a long integer handle value for use later on.

SoundHandle& = _SNDOPEN(FileName$)

The _SNDOPEN statement can load WAV, OGG, and MP3 sound file types. If _SNDOPEN returns a value of 0 it means there was an error opening the sound file. You should always check that the long integer variable you assign as a handle is greater than 0 before attempting to play the sound. Using the PLAY statement earlier we played the William Tell Overture. Let's try that again using an external sound file. Save the following example as WTOBetter.BAS.




In line 8 the file WilliamTell.OGG was loaded into memory and given the handle of WilliamTell& to be referenced later on. The _SNDPLAY statement in line 9 (_SNDPLAY discussed later) used that handle as a reference to start playing the file in memory.

Even though QB64 supports WAV and MP3 sound files I've personally found that OGG files work best, especially when the sound file's attributes such as volume or balance need to be altered. You can obtain a free program called Audacity that among other things converts between different sound types easily and quickly.



The _SNDPLAY  and _SNDPLAYCOPY Statements


The _SNDPLAY statement will play a sound file by using the handle value previously set with the _SNDOPEN statement. Once _SNDPLAY starts playing a file it will continue to play in the background until the sound file has completed or was told to stop using code.

In the example code included with the _SNDOPEN section above line 9 shows _SNDPLAY starting a previously loaded sound file:

_SDNPLAY WilliamTell& ' play OGG file from RAM

The _SNDPLAYCOPY statement plays a copy of a sound file loaded into RAM. _SNDPLAYCOPY is useful for situations where multiple copies of the sound need to be playing at once such as enemy explosions in a game. The example code below illustrates the difference between _SNDPLAY and _SNDPLAYCOPY. Save the code as SNDPLAYCOPYDemo.BAS.




There is a definite difference in sound quality and depth between the two statements. _SNDCOPY when asked to play a sound again will stop the same previous sound if it is still playing. _SNDPLAYCOPY however will play an entirely new copy of the same sound file allowing any previous sounds to complete. This allows the same sound file to overlap adding depth.

_SNDPLAYCOPY also has an optional volume control parameter that can control the volume of copied sounds. Line 17 of the example:

_SDNPLAYCOPY Phaser&, .75

is playing the copied sound file at 75% volume. The optional volume value ca be from 0 (no sound) to 1 (full sound). Controlling the volume of multiple copies can lead to some interesting effects such as this echo demonstrated in the next example. Save the code as EchoDemo.BAS when finished.




With the use of timing or frame counting within a game this sort of echo effect could easily be incorporated.


The _SNDLOOP Statement


The _SNDLOOP statement is used to start playing a sound file in a continuous loop. This is handy for sound such as ambient "mood" music in the background of games, or the "blip", "blip", blip" of a radar screen. Here is an example of keeping a drum beat going forever from a 3 second OGG sound clip. Save the code as SNDLOOPDemo.BAS when finished.




The _SNDPLAYFILE Statement


The _SNDPLAYFILE statement is used to play a sound without having to first load it into memory and generating a handle value for it. This would be use for "cut scenes" in a game where perhaps a narrator explains what has happened up to this point in a game. For sounds that are used over and over again, such as the rapid firing of a gun, it's best to use _SNDOPEN and _SNDPLAY or _SNDPLAYCOPY.

_SNDPLAYFILE will open the sound file, play it, and close it without any further interaction needed from the code. The statement will not return any errors as well. If the sound file is not found, or there was an error loading it, _SNDPLAYFILE will simply do nothing.

_SNDPLAYFILE ".\tutorial\task14\WilliamTell.OGG", , .5

_SNDPLAYFILE also has an optional volume parameter than can be added to control the volume in the same way _SNDPLAYCOPY does. Notice that you'll need to use two commas however.



The _SNDPLAYING Statement


The _SNDPLAYING statement is used to determine is a sound is currently playing, returning a value of 0 (false) if it is not and and a value of -1 (true) if it is. In the example code provided in the _SNDOPEN statement line 11 is used to check if the William Tell Overture sound file is still playing or not:

IF _SNDPLAYING(WilliamTell&) THEN _SNDSTOP WilliamTell& ' stop sound if playing

This statement can also be useful to move onto to new sections of your game based on a narrator finishing a talk for example.

_SNDPLAY Narrator1&
DO
_LIMIT 10 ' don't hog the CPU
LOOP UNTIL _KEYHIT OR NOT _SNDPLAYING(Narrator1&)


Here the code waits for either a key being pressed or the sound file finishing.



The _SNDSTOP Statement


The _SNDSTOP statement is used to stop a file from playing. It's usually best to check if the file is actually playing first before issuing the _SNDSTOP statement.

IF _SNDPLAYING(MySound&) THEN _SNDSTOP MySound& ' stop sound if playing

Note that _SNDSTOP will not work on sounds playing that were started with _SNDPLAYFILE.



The _SNDVOL Statement


The _SNDVOL statement is used to control the volume level of a sound even if it's currently playing. The following example code will allow you to change the volume of the sound playing by using the UP and DOWN arrow keys on the keyboard. Save the example as VolumeControl.BAS when finished.



The value supplied to _SNDVOL must be from 0 (no sound) to 1 (full sound) therefore a value of .25 would be equal to 25% volume. If you attempt to supply a value beyond this range a run-time error will occur in your program.


The _SNDPAUSE and _SNDPAUSED Statements


The _SNDPAUSE statement is used to pause a sound that is currently playing. _SNDPLAY can be used to start the sound once again from where it was paused.

IF _SNDPLAYING(MySound&) THEN _SNDPAUSE MySound& ' pause the sound if playing
SLEEP '                                            wait for a key stroke
IF _SNDPAUSED(MySound&) THEN _SNDPLAY MySound& '   start sound where pause left off


The _SNDPAUSED statement is used to test if a sound has been paused and returns a 0 (false) if the sound is not paused and -1 (true) if a sound is currently paused.



The _SNDLIMIT Statement


The _SNDLIMIT statement is used to limit the number of seconds a sound is allowed to play. _SNDLIMIT will not work on sounds that were started using _SNDLOOP. The example below shows a sound being limited to 5 seconds of play time. Save the code as SNDLIMITDemo.BAS when finished.



To remove the limit you can either set the limit to 0, pause, or stop the sound.


The _SNDLEN Statement


The _SNDLEN statement returns the number of seconds contained in a sound file by supplying the file handle created with an _SNDOPEN statement.

Seconds! = _SNDLEN(WilliamTell&)



The _SNDSETPOS and _SNDGETPOS Statements


The _SNDSETPOS statement sets the starting point in seconds of where to start playing a sound file from. _SNDGETPOS returns the current position within a sound file being played. The example below shows these two statements in action. Save the code as SNDPOSDemo.BAS when finished.



_SNDSETPOS requires a single value and the _SNDGETPOS statement returns single value. If you exceed the number of seconds contained in a sound file using _SNDSETPOS then playback will be interrupted.


The _SNDCLOSE Statement


The _SNDCLOSE statement is used to remove a sound file from RAM. It's good programming practice to clean up after yourself and remove all assets such as sound files and graphics files your program loaded before it terminates.

_SNDCLOSE WilliamTell& ' remove sound file from memory



A Sound Programming Example


The following code example shows how external sound files can be used to achieve impressive results. A piano has 88 keys and therefore 88 distinct tones. Each of these piano tones is contained in a small OGG file and loaded into RAM. By pressing a corresponding key on the keyboard a piano note is heard corresponding to the piano keyboard. In just a few lines of code a simulated piano is born. The keyboard keys to use the piano are as follows:

ESC          - exit the program
RIGHT ARROW  - increase piano octave
LEFT ARROW   - decrease piano octave
(piano keys) -   R T  U I O    (black piano keys)
                D F GH J K L   (white piano keys)


The code has been included in the .\tutorial\task14 directory as Piano.BAS.




Figure 1 - The QB64 Piano Example


Your Turn


Coming soon






Command Reference


New commands introduced in this task:

BEEP
SOUND
PLAY
_SNDOPEN
_SNDPLAY
_SNDPLAYCOPY
_SNDLOOP
_SNDPLAYFILE
_SNDPLAYING
_SNDSTOP
_SNDVOL
_SNDPAUSE
_SNDPAUSED
_SNDLIMIT
_SNDLEN
_SNDSETPOS
_SNDGETPOS
_SNDCLOSE


New concepts introduced in this task:

FM Synthesis
MIDI
WAV
OGG
MP3




Commands learned in Task 13:

_DIREXISTS
_FILEEXISTS
CHDIR
MKDIR
RMDIR
KILL
NAME...AS
OPEN
INPUT (file statement)
OUTPUT
APPEND
BINARY
RANDOM
PRINT (file statement)
WRITE (file statement)
LINE INPUT (file statement)
FREEFILE
LOF


Concepts learned in Task 13:

Sequential File
Comma Separated Value File (CSV)
Records
Database




Commands learned in Task 12:

AND
OR
XOR
NOT
\ (Integer Division)
MOD
INT()
CINT()
CLNG()
CSNG()
CDBL()
_ROUND()
FIX()
ABS()
SGN()
SIN()
COS()
TAN()
ATN()
READ
DATA


Concepts learned in Task 12:

numbering system
transistor
binary
binary numbering system
George Boole
Boolean logic
logic gate
bitwise math
flags
Banker's Rounding
sine
radian
cosine
tangent
arctangent



Commands learned in Task 11:

UCASE$()
LCASE$()
LTRIM$()
RTRIM$()
_TRIM$()
INSTR()
STR$()
DATE$
LEFT$()
RIGHT$()
MID$()
ASC()
STRING$()
SPACE$()
SWAP


Concepts learned in Task 11:

parse



Commands learned in Task 10:

DIM
TYPE
END TYPE
AS
REDIM
_PRESERVE
UBOUND
LBOUND
OPTION BASE


Concepts learned in Task 10:

array
one dimensional array
element
index
two dimensional array
three dimensional array
Dynamic Array
Static Array




Commands learned in Task 9:

LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON


Concepts learned in Task 9:

BIOS
ASCII
buffer
CMOS
ROM
ASCII chart



Commands learned in Task 8:

SUB
END SUB
FUNCTION
END FUNCTION
SHARED

Concepts learned in Task 8:

subroutine
function
local variables
global variables
Pythagorean Theorem



Commands learned in Task 7:

SCREEN
LINE
CIRCLE
PAINT
PSET
_RGB32()


Concepts learned in Task 7:

pixels
radian
counter-clockwise
aspect ratio


Commands learned in Task 6:

FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP (and variations)
WHILE...WEND

Concepts learned in Task 6:

frame rate
controlled loops
conditional loops


Commands learned in Task 5:

TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )

Concepts learned in Task 5:

relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels


Commands learned in Task 4:

INPUT
DIM
REM or '
CONST

Concepts learned in Task 4:

variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant


Commands learned in Task 3:

PRINT

Concepts learned in Task 3:

execute
statement
expression
literal string
syntax error