Playing sound
Volume Number: | | 7
|
Issue Number: | | 1
|
Column Tag: | | XCMD Corner
|
Playing With Sound
By Donald Koscheka, Contributing Editor
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
The Importance of XCMDs
One of the questions that is raised by Hypercard 2.0s new XCMD interface is, How important are XCMDs to the vitality of the Macintosh?. One cannot imagine that XCMDs would be very important at all so it would be useful to the xcmd programmer to ponder the future of xcmds in the Macintosh programming world.
After studying Hypercard 2.0 in great detail, Ive concluded that XCMDs are very important to the success of this product and that Apple Computer, Inc. is acknowledging this by providing more services to the XCMD programmer such as windoids and script manipulation capability. But another event in the small computer world lends even more credence to XCMD as programming art. That event is quietly taking over the MS-DOS world as Microsoft successfully evangelizes the concept of Dynamic Link Libraries, or DLLs.
If youre not a PC programmer (and who can afford not to be), then you may not know that DLLs are code modules that are written in such a way as to be loadable and thus callable at any time. DLLs are code segments that have a standard programming interface associated with them. In effect, DLLs allow you to build re-usable code modules that in theory can be used in any application. Well, if every application had such an interface on the Mac, lets call it XCmdPtr, then xcmds would serve the same purpose -- the ability to call xcmds from any program that runs on the mac. Without a lot of induction, one can come to the conclusion that if xcmds can give you this type of capability on the Mac, then they are indeed important, at least at the conceptual level.
If xcmds become callable from any application then they become an integral part of the future of the Macintosh. It is not unimaginable to believe that someday you will flesh-out your application with some sort of screen generator and then build a set of xcmds to implement the application-specific code. I find it impossible to believe that this effective approach to programming wont become the norm for all programmers in the future. In fact, Michael Swain suggests exactly this approach for PC programmers using ToolBook ( Dr. Dobbs Journal, November, 1990). When I read this column, my eyes popped out of my head. Mr. Swains concept of marrying rapid prototyping with rapid code development via DLLs is exactly what weve been doing with Hypercard for the last three years. Any Windows developer that takes his advice will wonder what all the fuss was about; Windows will appear to be an easy and natural development environment.
So now I can rest easy. I am convinced that the XCMD approach to programming will prevail over time. Its working for me. By forcing me to avoid globals and to think modularly, XCMDs have allowed me to write code that is re-usable in a wide-variety of applications. Each time I write an XCMD, I create a piece of code that I not only know I will use again but that I can comfortably extract from Hypercard without worrying about breaking it. That is the programmers holy grail and that is why XCMDs not only have a future, they are the future.
Playsound
Several months ago, I got a call from Bill Leitman, a fellow Mac developer in the New York area. He was working with a small film company in Brooklyn that needed an xcmd that played a portion of a sound resource. Bills approach was quite reasonable, he took a piece of code that he knew to be working from another application and dropped it into an xcmd. Lo and behold, it didnt work. He called me in desperation to see if there was anything that I knew about xcmds that was causing his problem. To no avail, I also tried to get the sound manager to work from within an xcmd but after hours of head scratching and staring at TMON windows, I came to the conclusion that Hypercard just wasnt going to let me use the sound manager.
This was unfortunate and unbelievable, but Ive never been able to find a way to get the sampled synth playing in an xcmd. Imagine my surprise when I first encountered the following callbacks in Hypercard 2.0: BEGINXSOUND and ENDXSOUND. Is it possible that it wasnt me? Did Hypercard actually work the way I thought it did by taking over the sound driver and not making it available to anyone else. I dont know the answer to this question, tho Id like to so please write if you have some input. I do know that this must have been a problem since Apple went to the motions of adding these callbacks, which work quite perfectly for me.
This months playsound xcmd (listing 1) was inspired by Mr. Leitman and I am grateful to him that he gave me an excuse to explore the sound manager. Its more daunting than the Sunday Times crossword puzzle and just as much fun!
Playsound takes up to four parameters: the first is the name of the file to retrieve the sound resource from. The second parameter is the name of the sound resource to retrieve. The third parameter is the index to the sample to start playing from (0 means play from the beginning of the sound). The fourth parameter is the last sample to play to. These last two parameters are optional, if they are left out, the entire sound resource will be played.
For simplicity, this code makes two assumptions that you can modify to meet your own needs: (1) you want to play the sound using the sampled synthesizer and (2) the sound is a format 2 snd resource. I chose not to support format 1 snd resources because Inside Macintosh indicates that format 2 snd resources should be adopted by developers as a standard. Should you decide that you want to support format 1 resources, refer to page 491 of Inside Macintosh Volume V for details on that format.
The xcmd parses out the parameters, opens the appropriate resource file and loads the snd resource which it then detaches from the resource map. This is important because we are going to modify the resource and we dont want the changes to be written back out by mistake. We call BEGINXSOUND to advise hypercard that we want to take over the sound drive for a while. Next we call PlayRunSound to play the sound, or sound fragment, and then we call ENDXSOUND to give the sound driver back to Hypercard.
PlayRunSound() is the heart and soul of this xcmd. The first thing that it does is check to see if the appropriate synthesizer is available. If so, it then allocates a new channel via the SndNewChannel call. This call returns a pointer to our new channel, initialized for the sampled synthesizer in theChan. We will pass this pointer to SndDoCommand to play this sound on that channel.
Once the channel is initialized and we know that we can use it, we set the start and stop time of the sound and play it. The first thing we need to do is check to see if we have the correct resource format which we do by testing to make sure that the first word in the resource (sndHdl) is set to 2. If it is, then we can access the fields in the snd resource by referring to the documentation on page 494 of IM vol V.
Figure 1. A typical format 2 snd resource.
Here is how the sound fragment scheme works: The snd resource contains some header information (6 bytes) followed by a number of 8 byte sound commands (the sample sound resource in figure 1 contains only one sound command). The sound data immediately follows the sound commands. If there is only one sound command, then the sampled data table will begin at offset 14 (0x0E) in the record.
The first two long words in the sound data table tell us where the samples start and how many of them there are. The where is usually set to 0 and the how much is usually set to the number of samples to play back. We can modify these fields to change the starting point and the number of samples to play. Or, we can set just the new starting point and play back to the end of the sound. Playsound doesnt use the sound command (in this case, soundCmd) but rather uses the bufferCmd instead.
So, we modify the sound table by stuffing new values for the pointer and length and then call the sound manager via the bufferCmd. Once the sound is done playing, we call SndDisposeChannel to remove our allocated channel, in effect freeing up the sampled synthesizer in preparing to give it back to Hypercard. Thats all there is to it. You might want to use this xcmd to continue to explore the sound manager. It appears to have rich and intriguing repertoire. In fact, I hope to present more on the sound manager in the future. In the meantime, I will continue to illuminate Hypercard 2.0. Happy Hacking.
Listing 1: PlaySndFrag.c
/********************************/
/* File: PlaySndFrag.c */
/********************************/
#define UsingHypercard
#include <SoundMgr.h>
#include<SetupA4.h>
#include <HyperXCmd.h>
/* usage: playSndFrag fileName, soundName, start ,stop
project requires the following files or libraries:
ANSI-A4-- the A4 based standard C libraries
HyperXLib-- the xcmd callback glue
MacTraps -- what it is
PlaySnd.c-- this file */
void putResult( XCmdPtr pp, char *msg);
void PlayRunSound( Handle sndHdl, long start, long stop );
long paramtoNum( XCmdPtr pp, short i );
void paramtoPString( XCmdPtr pp, short i, char *str );
Handle strToParam( char *str );
#ifndef NIL
#defineNIL (void *)0L
#endif
pascal void main( XCmdPtr pp )
/******************************************
* The main entry point
******************************************/
{
short resFile; /*** resource file holding snd ***/
short saveResFile; /*** previous resource file id ***/
Handle sndHdl; /*** handle to the sound resource ***/
long start; /*** start sample in sound ***/
long stop; /*** end sample in sound ***/
char *temp[256];
/*** for converting to pascal strings ***/
pp->returnValue = NIL; /* empty return means OK */
if (pp->paramCount == 1){
if ( **(pp->params[0]) == ! ){
pp->returnValue = strToParam(Play Sound XCMD, version 1.0, ©Donald
Koscheka, 1990);
return;
}
if ( **(pp->params[0]) == ? ){
pp->returnValue = strToParam(PlaySound FILE, snd rsrc id [,start][,stop]);
return;
}
}
paramtoPString( pp, 0, (char *)&temp );
start = paramtoNum( pp, 2 );
stop = paramtoNum( pp, 3 );
saveResFile = CurResFile(); /* save current res file id */
resFile = OpenResFile( temp );
if (resFile == -1) {
putResult( pp, cant open resource file );
return;
}
UseResFile( resFile ); /* in case it was already open */
paramtoPString( pp, 1, (char *)&temp );
sndHdl = Get1NamedResource( snd , &temp );
/* getting the resource ok */
if(!sndHdl){
putResult( pp, no such snd );
UseResFile( saveResFile );
return;
}
BEGINXSOUND( pp, NIL );
DetachResource( sndHdl );
PlayRunSound( sndHdl, start, stop );
DisposHandle( sndHdl );
ENDXSOUND( pp );
UseResFile( saveResFile );
}
void PlayRunSound( sndHdl, start, stop )
Handle sndHdl;
long start;
long stop;
/**********************************
* st[0] determines whether we have a
* format 2 or a format 1 snd resource.
* currently use the 44K sampler. Refer to Inside Macintosh Vol V p.494
for the format of a format 2 snd resource
**********************************/
{
short *st;
long *lt;
SndCommand sndCmd;
long idx;
SndChannelPtr theChan = NIL;
OSErr err;
sndCmd.cmd = availableCmd;
if( err = SndControl( sampledSynth, &sndCmd ) ) return;
if( err = SndNewChannel( &theChan, sampledSynth, initSRate44k, NIL )
) return;
HLock( sndHdl );
st = (short *)*sndHdl;
sndCmd.cmd = bufferCmd;
sndCmd.param1 = 0L;
if( *st == 2){
idx = 6L + ((long)st[2] << 3);
/* offset to start of commands */
lt = *sndHdl + idx;
sndCmd.param2 = (long)lt;
if( start )
lt[0]= (long)*sndHdl + idx + start;
if( stop )
lt[1] = stop - start;
err = SndDoCommand( theChan, &sndCmd, FALSE );
err = SndDisposeChannel( theChan, FALSE );
}
HUnlock( sndHdl );
}
void putResult( XCmdPtr pp , char *msg )
{
if (pp->returnValue)
DisposHandle(pp->returnValue);
pp->returnValue = (Handle)NewHandle(1 + strlen(msg) );
strcpy( *(pp->returnValue), msg);
}
Handle strToParam( char *str )
/***************************
* Given a pointer to a string, copy that string into a handle
* and return the handle.
* The input and output strings are both null-terminated
***************************/
{
Handle outH = NIL;
long len = 0;
len = strlen( str );
if( len )
if( outH = NewHandle( len ) )
BlockMove( str, *outH, len + 1 );
return( outH );
}
void paramtoPString( XCmdPtr pp, short i, char *str )
/************************
* Given an index into the parameter list, convert the data
* in the Block into a pstring
*
* This sequence is so common in XCMDs that it makes sense to make it
a subr. This is a generic routine that you can use in any xcmd.
************************/
{
if( pp->params[i] ){
HLock( pp->params[i] );
ZEROTOPAS( pp, (Ptr)*(pp->params[i]), (StringPtr)str );
HUnlock( pp->params[i] );
}
else
*str = \0;
}
long paramtoNum( XCmdPtr pp, short i )
/************************
* Given a Block to an input argument in the paramBlk
* return an integer representation of the data.
************************/
{
char theStr[32];
theStr[0] = \0;
if( pp->params[i] ){
HLock( pp->params[ i ] );
ZEROTOPAS( pp, (char *)*(pp->params[ i ]), theStr );
HUnlock( pp->params[ i ] );
return STRTOLONG( pp, theStr );
}
return 0L;
}