|
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 doesn’t 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 MBPC’s, which can be added to QuickBasic itself and/or applications it creates. MBPC’s 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, DA’s, etc.), but when necessary for speed or language extension, I can write simple, short compiled routines.
In this article I use MBPC’s to demonstrate one of the things that you can’t 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 Murphy’s 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 QuickBasic’s ToolBox command -- but it’s so simple that it’s 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 doesn’t 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 don’t want to do this, or if you want to distribute a program to others who will use it under interpreted QuickBasic, you’ll 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 don’t 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 isn’t difficult, but it’s significantly more complex than just changing the speaker volume, and I’m grateful for the guidance provided by MacTutor authors before me. In particular, Todd Carper’s 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. There’s no return parameter provided, so if there’s 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 snd’s 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 aren’t 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
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine