Digitized Sound
Volume Number: | | 6
|
Issue Number: | | 5
|
Column Tag: | | Basic School
|
Related Info: Sound Manager
Playing Digitized Sound
in QuickBasic
By Robert Spencer, East Lyme, CT
Digitized Sound in QuickBasic
While Microsoft QuickBasic is my favorite language for getting things done quickly, it doesnt have the power to produce applications as rich in Mac features as Pascal or C. Fortunately, Microsoft gives us the ability to add to the language with pure code resources, or MBPCs, which can be added to QuickBasic itself and/or applications it creates. MBPCs themselves must be written in Pascal or C, and compiled with the glue routines that QuickBasic needs. Once added to the resource fork of QuickBasic, an MBPC simply adds a new statement to the language. When this statement is encountered, control is passed to the compiled code. From my point of view this is a great way to work -- I get the quick turnaround time and comforting environment of an interpreter (it takes care of the user interface, memory management, printing, MultiFinder, DAs, etc.), but when necessary for speed or language extension, I can write simple, short compiled routines.
In this article I use MBPCs to demonstrate one of the things that you cant do directly in QuickBasic: play digitized sound -- not just MacinTalk-like speech, but the high-quality music and voices that add so much pizzazz to HyperCard and games.
The QuickBasic manual and disks give examples for code resource writing with MPW Pascal, MPW C, and Lightspeed C. As Murphys Law would have it, my second language is THINK Pascal -- but fortunately the MPW Pascal glue routines supplied by Microsoft work without modification in THINK Pascal 2.0. The two MBPC resources shown below are SetSoundVol, which allows you to set the speaker volume from within QuickBasic, and PlaySound, which lets you play any digitized snd resource (note the trailing space in snd ). These new commands can be used in either interpreted or compiled Microsoft QuickBasic 1.0.
Changing the Speaker Volume
First is SetSoundVol, a minimal MBPC. The speaker volume could be changed without writing an MBPC, by using QuickBasics ToolBox command -- but its so simple that its a good starting example for MBPC programming.
The code (listing 1) is straightforward since there are only three lines that do anything. First, the glue routines GetNextLibArg and IntegerArg pass the desired sound volume (an integer 0,1,..,7) from QuickBasic, and then SetSoundVol(MyVolume) does the work. There is no error checking or trapping, so you should make sure the passed number is a legal value. Note also that the number can be passed by value or by name, since the routine doesnt try to change it.
Build the project as shown in the SetVolume.Π project window. The DRVRRuntime.lib library is necessary for pure code resources; both it and Interface.lib come with THINK Pascal. BasicLib.a.o and BasicLibMPWP.p are the glue files for MPW Pascal that come with QuickBasic, and they link without modification under THINK Pascal. Finally, before compiling, fill in the Set Project Type... dialog for a Code Resource. This is shown for the PlaySound project; for SetSoundVol, just change the resource name to SetSoundVol and give it a different ID number. After compiling and linking, use ResEdit to copy SetSoundVol and then paste it into QuickBasic. If you dont want to do this, or if you want to distribute a program to others who will use it under interpreted QuickBasic, youll have to include a LIBRARY statement in your Basic programs that refers to a separate file with SetSoundVol in its resource fork.
For a very simple demo of SetSoundVol, the following program is hard to beat:
'1
SetSoundVol minimal demo
Uncomment the LIBRARY command
if you dont insert the MBPC into QB
LIBRARY MyMBPCFileName$
FOR i = 1 to 5
SetSoundVol 1 : BEEP
SetSoundVol 7 : BEEP
NEXT i
END
For a better demo, see listing 3, the QuickBasic program that demonstrates PlaySound, since it also uses SetSoundVol.
Playing Digitized Sounds
Playing digitized sounds isnt difficult, but its significantly more complex than just changing the speaker volume, and Im grateful for the guidance provided by MacTutor authors before me. In particular, Todd Carpers 4th Dimension externals for playing sound (MacTutor 9, 42, and The Definitive MacTutor IV, 356-363) are in the same spirit as this QuickBasic MBPC (listing 2).
To keep things simple, let QuickBasic do as much of the work as possible. I assume that QuickBasic has opened the file(s) containing the snd resources required (the System file and the current application are automatically opened), and I also let QuickBasic determine the snd resource numbers. In the demo program I put all available sounds in a menu, so I use CountRes, GetIndRes, and GetResInfo. If you wanted a specific named snd, you could use GetNamedRes and GetResInfo to get the resource number. Note that these statements are part of the QuickBasic Toolbox Library, so be explicit in typing your integers% and longints&. The ratio% parameter should have a value of 1,2,3, or 4, depending on the sampling frequency used when the snd was recorded (22, 11, 7.3, or 5.5 kHz, respectively). Essentially, ratio% just determines the playback speed ( 1 = fast, 4 = slow), and you can experiment with it to find out what speed is correct for a given snd. Theres no return parameter provided, so if theres an error (e.g. a bogus resource number), then nothing happens -- simply no sound plays (Zen error handling?).
My favorite place to find unusual snd resources is in HyperCard stacks. You can open the resource fork of a stack directly and look for snds there; for example, inserting
' 2
FileRef% = 0 initialize FileRef%
OpenResFile hard disk:HyperCard folder:Sound Stack,FileRef%
into the demo program would add the snd resources in hard disk:HyperCard folder:Sound Stack to the Sounds menu. If you have sound resources but they arent of type snd , the Boston Computer Society distributes a useful stack and associated tools to change digitized sounds from one type to another.
Have fun!
Rob Spencer is a biochemist at Pfizer Central Research, Groton, CT. He has a B.A. in physics, a Ph.D. in biochemistry, and is fortunate to be able to combine his avocation (Mac programming) with his vocation (finding and assaying new drugs to treat human disease). He can be reached at (203) 441-3946.
Figure 1. The Set Project Type... dialog in THINK Pascal
Figure 2. SetVolume Project
listing 1 : SetVolume.p
UNIT SetVolume;
{ Called from QuickBasic as: }
{ SetSoundVol vol% }
{ where vol% = 0,1,...,7 }
INTERFACE
USES
BasicLib;
PROCEDURE main;
IMPLEMENTATION
PROCEDURE main;
VAR
tempflag, argtype, MyVolume: INT16;
valptr: LIBARGPTR;
BEGIN
argtype := GetNextLibArg(valptr, tempflag);
MyVolume := IntegerArg;
SetSoundVol(MyVolume);
END;
END.
Figure 3. The PlaySnd Project
listing 2: PlaySnd.p
UNIT PlaySoundUnit;
{ Called from QuickBASIC as: }
{ CALL PlaySound (SndResNum%, ratio%) }
{ in THINK Pascal by Rob Spencer 1/6/90 }
INTERFACE
USES
BasicLib;
PROCEDURE Main;
IMPLEMENTATION
PROCEDURE PlaySnd (SndResNum, Ratio: Integer);
{Thanks to Allan Wootton, The Complete MacTutor II, 230-235}
{and Todd Carper, The Definitive MacTutor IV, 356-363}
TYPE
FFSynthHandle = ^FFSynthPtr;
VAR
MySndHandle: FFSynthHandle;
MyPtr: Ptr;
MyFFPtr: FFSynthPtr;
MyRatio: integer;
BEGIN
IF (Ratio > 0) AND (Ratio < 5) THEN
{ ratio must be 1..4 }
MyRatio := Ratio
ELSE
MyRatio := 2; { an arbitrary default }
MySndHandle := FFSynthHandle(GetResource(snd , SndResNum));
IF MySndHandle <> NIL THEN
BEGIN
Hlock(Handle(MySndHandle));
MyPtr := Ptr(MySndHandle^);
MyFFPtr := FFSynthPtr(MyPtr);
MyFFPtr^.mode := FFMode;
MyFFPtr^.count := FixRatio(1, MyRatio);
MyFFPtr^.wavebytes[0] := 0;
StartSound(MyPtr, GetHandleSize(Handle(MySndHandle)) - 6, pointer(-1));
StopSound;
HUnlock(Handle(MySndHandle));
DisposPtr(MyPtr);
END;
END; { of PlaySnd }
{------------------ main ------------------}
PROCEDURE main;
VAR
tempflag, argtype: INT16;
valptr: LIBARGPTR;
SndResNum, ratio: integer;
BEGIN
argtype := GetNextLibArg(valptr, tempflag);
SndResNum := IntegerArg; { get argument 1 }
argtype := GetNextLibArg(valptr, tempflag);
ratio := IntegerArg; { get argument 2 }
PlaySnd(SndResNum, ratio);
END;
END.
listing 3 : PlaySound Test Program
PlaySound Test Program
This QuickBASIC program will play snd
resources from the resource fork of
the application (either QB or itself if
compiled) and the System file
(automatically opened).
Rob Spencer January 1990
GOSUB Initialize
WHILE NOT AllDone% main event loop
CheckEvent
WEND
dummy = FRE(-1) compact the heap
END
_________________ subroutines ______________
Initialize:
Uncomment the LIBRARY statement if the
MBPCs SetSoundVol and PlaySound are in
a separate MBPC file.
LIBRARY MyMBPCLibName$
DEFINT i-n
use DIM SHARED to initialize variables
as required by ToolBox Library statements
DIM SHARED SndResNum%(21),handle&,NumSnds%
DIM SHARED volume%,ratio%,dummy$,AllDone%,True%,False%
False% = 0
True% = NOT False%
AllDone% = False%
SndName$ =
Uncomment the following to use a
separate snd file
FileRef% = 0
OpenResFile YourSndFileName$,FileRef%
CountRes snd ,NumSnds%
limit to 20 items in a menu
IF NumSnds%>20 THEN NumSnds% = 20
set up menus
MENU 1,0,1,File
MENU 1,1,1,Quit
CmdKey 1,1,Q
MENU 3,0,1,Sounds
FOR i% = 1 TO NumSnds%
GetIndRes snd ,i%,handle&
GetResInfo handle&,SndResNum%(i%),dummy$,SndName$
MENU 3,i%,1,SndName$
NEXT i%
MENU 4,0,1,Volume
FOR i = 0 TO 7
MENU 4,i+1,1,STR$(i)
NEXT i
MENU 5,0,1,Compression
FOR i = 1 TO 4
MENU 5,i,1,STR$(i)
NEXT i
MENU 6,0,1,Combos
MENU 6,1,1,random
ratio% = 2 set defaults
MENU 5,2,2
volume% = 5
MENU 4,4,2
SetSoundVol volume%
dummy = FRE(-1)
RETURN
SUB CheckEvent STATIC only menu events
MenuId% = MENU(0)
ItemId% = MENU(1)
SELECT CASE MenuId%
CASE 1
AllDone% = True%
CASE 3
PlaySound (SndResNum%(ItemId%)),(ratio%)
CASE 4
FOR i = 0 TO 7 uncheck all
MENU 4,i+1,1
NEXT i
MENU 4,ItemId%,2 check ours
volume% = ItemId%-1
SetSoundVol (volume%)
CASE 5
ratio% = ItemId%
FOR i = 1 TO 4 uncheck all
MENU 5,i,1
NEXT i
MENU 5,ItemId%,2 check ours
CASE 6
FOR i% = 1 TO 10 play 10 snd s
item% = NumSnds%*RND
PlaySound (SndResNum%(item%)),(ratio%)
NEXT i%
CASE ELSE
END SELECT
IF MenuId%>0 THEN MENU MenuId%,0,1
END SUB