home *** CD-ROM | disk | FTP | other *** search
/ Amiga MA Magazine 1998 #6 / amigamamagazinepolishissue1998.iso / coders / jËzyki_programowania / logo / powerlogo / sound / sound.c < prev    next >
C/C++ Source or Header  |  1992-11-14  |  18KB  |  565 lines

  1. /****************************   Sound.c   *********************************
  2.  
  3.     Sound is copyright (c) 1988 by Richard Lee Stockton, 21305 60th Ave W.,
  4. Mountlake Terrace, Washington 98043, 206/776-1253(voice), but may be freely
  5. distributed as long as no profit is made from its distribution or sale
  6. without my written permission. I call this concept 'FreeWare', you can
  7. call it whatcha want.
  8.  
  9.     I also include the source code, (Manx 3.4/3.6), in the hope that it
  10. will be of benefit to someone in the Amiga programming community. Feel free
  11. to alter it at will, (but at your own risk!). I _would_ appreciate
  12. receiving a copy of whatever it may become and it is always nice to receive
  13. credit, (perhaps just a few lines of glowing tribute.^)
  14.  
  15.                Long Live Leo and All the Little Schwabies!
  16.  
  17.                     To Manufacture with Manx 3.4/3.6:
  18.  
  19.                             cc Sound.c
  20.                             ln +cd Sound.o -lc
  21.  
  22.      You'll get a warning that you've re-defined IconBase. Ignore it.
  23.  
  24. **************************************************************************/
  25.  
  26. #include <exec/memory.h>
  27. #include <workbench/startup.h>
  28. #include <workbench/workbench.h>
  29. #include <workbench/icon.h>
  30. #include <libraries/dosextens.h>
  31. #include <devices/audio.h>
  32. #include <functions.h>
  33.  
  34. /* Less than WorkBench 1.2 need not apply. That's nobody, right? ;-> */
  35.  
  36. #define  REVISION   33L
  37.  
  38. /* We'll need 4 buffers in CHIP memory, all else in FAST, if ya got it */
  39.  
  40. #define  BUFSIZE  1024L
  41.  
  42. /* Some MANX specific, byte saving, (usually), string routines */
  43.  
  44. #define  strcpy     _BUILTIN_strcpy
  45. #define  strcmp     _BUILTIN_strcmp
  46. #define  strlen     _BUILTIN_strlen
  47.  
  48. /* A pretty little HELP message showing valid variable ranges */
  49.  
  50. #define  USAGE      "\
  51. \033[42m\033[31m\
  52.  USAGE: Sound <file> [l/r]  [0-55]   [56-65535]  [on/off]  [QUIET]  \
  53. \033[m\n\033[42m\
  54.  ABORT: <CTRL> 'c'\033[33m\
  55.   CHANNEL CYCLES SAMPLES/SECOND STEREO  NO-WINDOW \
  56. \033[m\n\033[42m\033[31m\
  57.  02-DEC-88 03:10 \xA9 1988 Richard Lee Stockton - freely distributable \
  58. \033[m\n"
  59.  
  60. /* Probably more GLOBALS than are required. 'C' is funny stuff. */
  61.  
  62. extern struct   IconBase      *IconBase=NULL;
  63. extern struct   IntuitionBase *IntuitionBase=NULL;
  64. extern struct   WBStartup     *WBenchMsg=NULL;
  65. extern struct   WBArg         *argp=NULL;
  66.        struct   IntuiMessage  *message=NULL;
  67.        struct   Window        *StatusWindo=NULL;
  68.        struct   DiskObject    *infofile=NULL;
  69.        struct   IOAudio       *sound[4]={NULL,NULL,NULL,NULL};
  70.                 long          sactual=0L, sstart=0L, savelock=0L,
  71.                                atol(), sps=0L, cycles=1L, lock=0L;
  72.                 short         k=0, stereo=0, left=1, right=1,
  73.                                statusline=1;
  74.                 UBYTE         sunit[4]={12,10,5,3};
  75.                 BOOL          help=FALSE;
  76.                 char          *sbuffer=NULL, *ltoa(), title[50]="Sound: ",
  77.                                *cbuf[4]={NULL,NULL,NULL,NULL},
  78.                                *sname[108]=NULL, *SafeAllocMem();
  79.                 void          loadSound(), setStatusWindo(),
  80.                                soundSound(), cleanup(), quit();
  81.  
  82. /*********** quit, give-up, go home, finish... Neatness counts! ******/
  83.  
  84. void quit(qstring)
  85. char    *qstring;
  86. { cleanup(qstring);  exit(0); }
  87.  
  88. void cleanup(string)
  89. char    *string;
  90. {
  91.    if(sound[0])      /* This cleans up the audio device stuff */
  92.    {
  93.       for(k=3;k>(-1);k--)    if(sound[k]) AbortIO(sound[k]);
  94.       if(sound[0]->ioa_Request.io_Device) CloseDevice(sound[0]);
  95.       for(k=3;k>(-1);k--)
  96.       {
  97.          if(sound[k]->ioa_Request.io_Message.mn_ReplyPort)
  98.             DeletePort(sound[k]->ioa_Request.io_Message.mn_ReplyPort);
  99.       }
  100.       for(k=3;k>(-1);k--)
  101.       {
  102.          if(sound[k]) FreeMem(sound[k],(long)sizeof(struct IOAudio));
  103.          if(cbuf[k])  FreeMem(cbuf[k],BUFSIZE);
  104.       }
  105.       sound[0]=sound[1]=sound[2]=NULL;
  106.    }
  107.  
  108. /* Write any message to out. May be error or could be samples/second */
  109. /* You'll be sorry if you try to Write(Output()) to WorkBench!  8-)  */
  110.  
  111.    if(help) Write(Output(),string,(long)strlen(string));
  112.    else if(strlen(string))
  113.    {
  114.       strcpy(title,"Sound Error: ");   strcat(title,string);
  115.       setStatusWindo();  Delay(150L);
  116.    }
  117.  
  118. /* Clean up everything else */
  119.  
  120.    if(StatusWindo)      CloseWindow(StatusWindo);
  121.    if(IntuitionBase)    CloseLibrary(IntuitionBase);
  122.    if(sbuffer)          FreeMem(sbuffer,sactual);
  123.    if(infofile)         FreeDiskObject(infofile);
  124.    if(IconBase)         CloseLibrary(IconBase);
  125.    if(!sound[3]) exit(10);
  126.    StatusWindo=NULL;    IntuitionBase=NULL;
  127.    sbuffer=NULL;   infofile=NULL;   IconBase=NULL;
  128.    sactual=0L; sstart=0L; savelock=0L; sps=0L; cycles=1L;
  129.    lock=0L;  k=0; stereo=0; left=1; right=1;
  130. }
  131.  
  132.  
  133. /*  Don't Allocate if Low Mem - by Bryce Nesbitt  */
  134. /* Aberations by RLS. 4096 should be fudge enough */
  135.  
  136. char *SafeAllocMem(size,flags)
  137. long size, flags;
  138. {
  139.    register char *p;
  140.    
  141.    if(p=(char *)AllocMem(size,flags))
  142.       if(AvailMem(MEMF_CHIP)<4096L)
  143.          { FreeMem(p,size);  return(NULL); }
  144.    return(p);
  145. }
  146.  
  147. /**** Status Window. Uses <CTRL> 'c' to abort.  ****/
  148.  
  149. void setStatusWindo()
  150. {
  151.     struct NewWindow w;
  152.     
  153. /* Try to Open Intuition and if successful, make a 'status' window */
  154.  
  155.     if(StatusWindo) CloseWindow(StatusWindo); StatusWindo=NULL;
  156.     if(!IntuitionBase)
  157.     {
  158.        IntuitionBase = (struct IntuitionBase *)
  159.                        OpenLibrary("intuition.library",REVISION);
  160.     }
  161.     if(!IntuitionBase) quit(NULL);
  162.  
  163.     w.LeftEdge    =   82L;
  164.     w.TopEdge     =    0L;
  165.  
  166. /* if title==USAGE, the large Width value will abort the window open */
  167. /* This is just fine, because we don't WANT it to open in this case. */
  168.  
  169.     w.Width       = (long)(strlen(title)*8+8);
  170.     w.Height      =   10L;
  171.     w.DetailPen   =  0x01;
  172.     w.BlockPen    =  0x03;
  173.     w.Title       = (UBYTE *)title;
  174.     w.Flags       = SMART_REFRESH|NOCAREREFRESH|ACTIVATE;
  175.     w.IDCMPFlags  = VANILLAKEY;   /* So we can get <CTRL> 'c' message */
  176.     w.Type        = WBENCHSCREEN;
  177.     w.FirstGadget = NULL;
  178.     w.CheckMark   = NULL;
  179.     w.Screen      = NULL;
  180.     w.BitMap      = NULL;
  181.     w.MinWidth    = 0;
  182.     w.MinHeight   = 0;
  183.     w.MaxWidth    = 0;
  184.     w.MaxHeight   = 0;
  185.     StatusWindo = OpenWindow(&w);
  186.  
  187. /* Abort ONLY if cycles = 0 (loop) AND the window failed to open. */
  188. /* This set of conditions would otherwise result in an Endless and */
  189. /* Escape-proof loop. If cycles != 0, sound will end, sometime... */
  190.  
  191.     if(!StatusWindo&&(cycles==0L)) quit(NULL);
  192. }
  193.  
  194.  
  195. /******** Load SoundFile 'sPath' & set cycles-sps-stereo *******/
  196.  
  197. void loadSound(sPath)
  198. char    *sPath[80];
  199. {
  200.    struct FileHandle    *sFile=NULL;
  201.    struct FileInfoBlock *finfo=NULL;
  202.    struct FileLock      *lock=NULL;
  203.           long          i, j;
  204.           char          string[5];
  205.  
  206. /* Allocate 256 bytes as work memory */
  207.  
  208.    if(!(sbuffer=SafeAllocMem(256L,MEMF_CLEAR|MEMF_PUBLIC)))
  209.         quit("No Work Memory!");
  210.  
  211. /* Check for and parse IFF data in first 256 bytes of file */
  212.  
  213.    if(!(sFile=Open(sPath,MODE_OLDFILE)))
  214.    {
  215.       sactual=256L;      quit("Can't Find SoundFile!");
  216.    }
  217.    Read(sFile,sbuffer,256L);              /* load the 1st 256 bytes */
  218.    for(sstart=0L, sps=0L, i=0L; i<252L; i+=4L)
  219.    {
  220.       strncpy(string,sbuffer+i,4);   string[4]=NULL;
  221.       if(!(strcmp(string,"VHDR")))        /* get samples per second */
  222.       {
  223.          for(j=0;j<(long)((UBYTE)sbuffer[i+20]);j++) sps+=256L;
  224.          sps += ((UBYTE)sbuffer[i+21L]);
  225.       }
  226.       if(!(strcmp(string,"CHAN")))            /* Channel Assignment */
  227.       {
  228.          if((sbuffer[i+7]==6)||(sbuffer[i+11]==6)) stereo=1;
  229.       }
  230.       if(!(strcmp(string,"BODY")))        /* get size of sound data */
  231.       {
  232.          for(j=0;j<4;j++) sactual+=(((UBYTE)sbuffer[i+7L-j])<<(8*j));
  233.          sstart = i+8L; i=252L;
  234.       }
  235.    }
  236.  
  237. /* if not in IFF format, get filesize from FileInfoBlock */
  238.  
  239.    if(!sactual)
  240.    {
  241.  
  242. /* Allocate a file info block, get size from it, and de-allocate */
  243.  
  244.       if((!(finfo=(struct FileInfoBlock *)
  245.          SafeAllocMem((long)sizeof(struct FileInfoBlock),MEMF_CLEAR)))
  246.        ||(!(lock=Lock(sname,ACCESS_READ)))||(!(Examine(lock,finfo))) )
  247.                     quit("FileInfoBlock Problem!");
  248.       sactual = finfo->fib_Size;      if(lock) UnLock(lock);
  249.       if(finfo) FreeMem(finfo,(long)sizeof(struct FileInfoBlock));
  250.    }
  251.  
  252. /* clean up work area */
  253.  
  254.    FreeMem(sbuffer,256L); sbuffer=NULL;
  255.  
  256. /* Allocate _contiguous_ memory for SOUND data. */
  257. /* We'll transfer in BUFSIZE chunks to CHIP memory a little later. */
  258. /* We have to do the contiguity(?) check since AllocMem() does not. */
  259.  
  260.    if((AvailMem(MEMF_LARGEST)<sactual) ||
  261.      (!(sbuffer=SafeAllocMem(sactual,MEMF_CLEAR|MEMF_PUBLIC))))
  262.         { Close(sFile); quit("Need Contiguous Memory!"); }
  263.  
  264. /* Load the data into sbuffer */
  265.  
  266.    Seek(sFile,sstart,OFFSET_BEGINNING);
  267.    if((Read(sFile,sbuffer,sactual)) == -1L)
  268.       {Close(sFile); quit("Read Error!");}
  269.    Close(sFile);
  270. }
  271.  
  272. /*****************  make a noise ******************/
  273.  
  274. void soundSound()
  275. {
  276.     ULONG   class;
  277.     LONG    i, dactual, dlength, remaining;
  278.     USHORT  code, count;
  279.  
  280. /* Put up a 'status' window on the top line. */
  281.  
  282.     strcpy(title,"Sound: ");
  283.     strcat(title,ltoa(sps)); strcat(title," SAMPLES_PER_SECOND");
  284.     if(stereo&&left&&right) strcat(title," in STEREO");
  285.     if(statusline) setStatusWindo();
  286.  
  287. /* Make sure we have valid values before 'sounding' */
  288.  
  289.     if(left==right) left=right=1;
  290.     if((sps<56L)||(sps>65534L)) sps = 10000L;
  291.     if(cycles>65535L) cycles = 1L;       /* 55 limit only applies to CLI */
  292.  
  293. /* Allocate sound data buffers from CHIP memory. Ports and */
  294. /* Audio Request Structures do NOT require CHIP memory */
  295.  
  296.    for(k=0;k<4;k++)
  297.    {
  298.      if(!(cbuf[k]=SafeAllocMem(BUFSIZE,
  299.            MEMF_CHIP|MEMF_CLEAR|MEMF_PUBLIC))) quit("No CHIP Memory!");
  300.      if(!(sound[k]=(struct IOAudio *)SafeAllocMem((long)sizeof(struct IOAudio),
  301.                      MEMF_CLEAR|MEMF_PUBLIC))) quit("No IOA Memory!");
  302.    }
  303.    if( (!(sound[0]->ioa_Request.io_Message.mn_ReplyPort =
  304.            CreatePort("Sound0",0L))) ||
  305.        (!(sound[1]->ioa_Request.io_Message.mn_ReplyPort =
  306.            CreatePort("Sound1",0L))) ||
  307.        (!(sound[2]->ioa_Request.io_Message.mn_ReplyPort =
  308.            CreatePort("Sound2",0L))) ||
  309.        (!(sound[3]->ioa_Request.io_Message.mn_ReplyPort =
  310.            CreatePort("Sound3",0L))) )         quit("No Port Memory!");
  311.    
  312.  
  313. /* Open Audio using the first IOAudio as the 'initializer' request */
  314.  
  315.    sound[0]->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
  316.    sound[0]->ioa_Data   = &sunit[0];
  317.    sound[0]->ioa_Length = 4L;
  318.    if((OpenDevice(AUDIONAME,0L,sound[0],0L))!=NULL)
  319.       quit("No Audio Device!");
  320.  
  321. /* Set all IOAudios. */
  322.  
  323.    for(k=0;k<4;k++)
  324.    {
  325.       sound[k]->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
  326.       sound[k]->ioa_Request.io_Command = CMD_WRITE;
  327.       sound[k]->ioa_Request.io_Flags   = ADIOF_PERVOL;
  328.  
  329. /* Note copies of Device & AllocKey from initializer. */
  330.  
  331.       sound[k]->ioa_Request.io_Device  = sound[0]->ioa_Request.io_Device;
  332.       sound[k]->ioa_AllocKey  = sound[0]->ioa_AllocKey;
  333.  
  334. /* Each IOAudio has its own CHIP buffer, Port, and Unit (left/right) */
  335.  
  336.       sound[k]->ioa_Data   = (UBYTE *)cbuf[k];
  337.  
  338. /* 3579547 divided by 55 = 65083, nearly the maximum Period (65535) */
  339.  
  340.       sound[k]->ioa_Period = 3579547L/sps;
  341.  
  342. /* As LOUD as possible. Use your monitor/stereo volume. Rock 'n Roll! */
  343.  
  344.       sound[k]->ioa_Volume = 64L;
  345.  
  346. /* One time through this BUFSIZE (or smaller) part of the whole */
  347.  
  348.       sound[k]->ioa_Cycles = 1L;
  349.    }
  350.  
  351. /* The compiler wants 'Unit' to be a structure, we just want to mask */
  352. /* into the allocated left/right channels. left=1 or 8, right=2 or 4 */
  353. /*        ...zap! You're a Unit structure! Feel any different?       */
  354.  
  355.    for(k=2;k>(-1);k-=2)
  356.    {
  357.       sound[k+1]->ioa_Request.io_Unit = (struct Unit *)
  358.                       ((ULONG)(sound[0]->ioa_Request.io_Unit)&6L);
  359.       sound[k]->ioa_Request.io_Unit  = (struct Unit *)
  360.                       ((ULONG)(sound[0]->ioa_Request.io_Unit)&9L);
  361.    }
  362.  
  363. /* If in STEREO, split file. If in MONO, 'b' buffers use 'a' data */
  364.  
  365.    if(stereo) remaining=(sactual/2L)-(sactual&1L);
  366.    else
  367.    {
  368.       remaining=sactual;
  369.       sound[1]->ioa_Data   = (UBYTE *)cbuf[0];
  370.       sound[3]->ioa_Data   = (UBYTE *)cbuf[2];
  371.    }
  372.  
  373. /* dactual is the length of one channel's complete data */
  374.  
  375.    dactual=remaining;     k=count=0;
  376.  
  377. /* we be doing loops here */
  378.  
  379.    do
  380.    {
  381.  
  382. /* be CERTAIN ioa_Length is an even number & set datalength */
  383.  
  384.       if(remaining>BUFSIZE) dlength=BUFSIZE;
  385.        else  { dlength=remaining;  dlength-=(dlength&1L); }
  386.  
  387. /* Move the data into the proper CHIP buffer of BUFSIZE */
  388.  
  389.       movmem(sbuffer+(dactual-remaining),cbuf[k],(int)dlength);
  390.  
  391. /* Don't load or use the right CHIP buffers if MONO. Saves time. */
  392.  
  393.       if(stereo) movmem(sbuffer+(sactual-remaining),
  394.                         cbuf[k+1],(int)dlength);
  395.  
  396. /* Data has been moved, so adjust 'remaining' */
  397.  
  398.       remaining-=dlength;
  399.  
  400. /* Left and Right Lengths are the same, no matter what! */
  401.  
  402.       sound[k]->ioa_Length = sound[k+1]->ioa_Length = dlength;
  403.  
  404. /* Start one set of Left/Right Channels. */
  405.  
  406.       if(left)  BeginIO(sound[k]);
  407.       if(right) BeginIO(sound[k+1]);
  408.  
  409. /* If no QUIET flag, Check Intuition for the ABORT message */
  410.  
  411.       if(statusline)
  412.       {
  413.       while(message=(struct IntuiMessage *)GetMsg(StatusWindo->UserPort))
  414.          {
  415.             class = message->Class;  code = message->Code;
  416.  
  417. /* Hi Intuition! Thanks for the message, Have a nice day! */
  418.  
  419.             ReplyMsg(message);
  420.  
  421. /* <CTRL> 'c' abort.  1,2,3, easy as a,b,c, baby you and me! */
  422.  
  423.             if((class==VANILLAKEY)&&(code==3)) quit(NULL);
  424.          }
  425.       }
  426.  
  427. /* Is this the last time AND the last cycle? If yes & no, reset. */
  428.  
  429.       if(remaining<2L) if(--cycles!=0L)
  430.          { remaining=dactual;   dlength=BUFSIZE; }
  431.  
  432. /* Is this the last time, or what? */
  433.  
  434.       if(remaining<2L)  WaitIO(sound[k+right]);   /* wait for LAST request */
  435.       else
  436.       {
  437.          if(k) k=0; else k=2;    /* switch buffers & wait for PREVIOUS */
  438.          if(count++) WaitIO(sound[k+right]);
  439.       }
  440.  
  441. /* Keep going until we run out of data */
  442.  
  443.    } while(remaining>1L);                   /* End of Loop */
  444. }
  445.  
  446.  
  447. /********** long int to char string (up to 7 digits) ***********/
  448.  
  449. char *ltoa(num)
  450.           long  num;
  451. {
  452.    static char ostring[8]="";
  453.           short  next = 7,  shift = 0;
  454.    
  455.    if (!num) ostring[next--] = '0';
  456.    while ((num+9L)/10)
  457.       { ostring[next--] = num % 10L + '0';   num /= 10L; }
  458.    next+=1;
  459.    while(next<8)       ostring[shift++] = ostring[next++];
  460.    while(shift<8)      ostring[shift++] = '\0';
  461.    return(ostring);
  462. }
  463.  
  464.  
  465. /************************  MAIN  ****************************/
  466.  
  467. main(argc,argv)
  468.  int           argc;
  469.  char          *argv[];
  470. {
  471.     long      temp=0L;
  472.     short     i=0, WBargs=0;
  473.     char      string[10];
  474.     
  475. /* If Called From CLI */
  476.  
  477.     if(argc==1) {help=TRUE; quit(USAGE);}    /* No SOUNDfile so quit */
  478.     if(argc>1)
  479.     {
  480.        strcpy(sname,argv[1]);   /* 2nd arg MUST be filename */
  481.        loadSound(sname);         /* Load 'em up! */
  482.        for(i=2;i<argc;i++)
  483.        {
  484.           if(argv[i][0]=='o'||argv[i][0]=='O')
  485.           {
  486.              if(argv[i][1]=='n'||argv[i][1]=='N')   stereo = 1;
  487.               else  stereo = 0;
  488.           }
  489.           else if(argv[i][0]=='l'||argv[i][0]=='L')      right = 0;
  490.           else if(argv[i][0]=='r'||argv[i][0]=='R')       left = 0;
  491.           else if(argv[i][0]=='q'||argv[i][0]=='Q') statusline = 0;
  492.           else  /* must be sps or cycles, size of arg determines which one */
  493.           {
  494.              temp = atol(argv[i]);
  495.              if(temp<56L)  cycles = temp;    else  sps = temp;
  496.           }
  497.        }
  498.        soundSound();   quit(NULL);
  499.     }
  500.  
  501.  
  502. /* If WorkBench, try to get info from the .info file */
  503.  
  504.     if(WBenchMsg->sm_NumArgs>1) WBargs=1;
  505.     while((argc==0)&&(WBenchMsg->sm_NumArgs>WBargs)) /*  from WorkBench  */
  506.     {
  507.        argp=(WBenchMsg->sm_ArgList)+WBargs;      /*  CD to lock and  */
  508.        if(lock=argp->wa_Lock)  savelock=(long)CurrentDir(lock);
  509.        strcpy(sname,argp->wa_Name);              /* get the filename */
  510.        loadSound(sname);
  511.        if(!IconBase)
  512.           IconBase=(struct IconBase *)OpenLibrary("icon.library",REVISION);
  513.        if((!IconBase) ||   
  514.             ((infofile=(struct DiskObject *)GetDiskObject(sname))==NULL))
  515.                 quit(NULL);
  516.  
  517. /* then check 'ToolTypes' to set cycles, stereo, & samples per second. */
  518. /* Note use of strncpy to copy only what we need. This is an attempt to */
  519. /* take care of a .info editing bug which may cause extra long strings. */
  520.  
  521.        strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
  522.                        "CYCLES"),3);
  523.  
  524. /* We do this check for '0' since atol() thinks ' ', or NULL = '0'  */
  525. /* But if data is blank, or NULL, we want cycles set to 1 (default) */
  526.  
  527.        if(string[0]=='0') cycles = 0L;
  528.        temp=atol(string);  if(temp) cycles = temp;
  529.  
  530. /* Valid STEREO strings will have o or O as the first letter. */
  531.  
  532.        strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
  533.                        "STEREO"),4);
  534.        if(string[0]=='o'||string[0]=='O')
  535.        {
  536.           if(string[1]=='n'||string[1]=='N')   stereo = 1;
  537.            else  stereo = 0;
  538.        }
  539.  
  540. /* Set left or right channel only (for special effects) */
  541.  
  542.        strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
  543.                        "CHANNEL"),4);
  544.        if(string[0]=='l'||string[0]=='L') right=0;
  545.        if(string[0]=='r'||string[0]=='R')  left=0;
  546.  
  547. /* We'll take any old sps because we check for validity before playing */
  548.  
  549.        strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
  550.                        "SAMPLES_PER_SECOND"),6);
  551.        temp = atol(string);    if(temp) sps = temp;
  552.  
  553.        soundSound();                                    /* MAKE THAT NOISE! */
  554.  
  555.        if(lock) CurrentDir(savelock);  lock=NULL;    /* restore current DIR */
  556.  
  557. /* Then release everything you've used here. (Be a tidy camper.) */
  558.  
  559.        cleanup(NULL);    WBargs++;
  560.     }
  561.     exit(0);
  562. }
  563.  
  564. /************************** end of Sound.c ******************************/
  565.