TweetFollow Us on Twitter

MACINTOSH C

Demonstration Program

Go to Contents
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// Sound.c
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
//
// This program opens a modal dialog containing eight bevel button controls arranged in 
// two groups, namely, a synchronous sound group and an asynchronous sound group.  
// Clicking on the bevel buttons causes sound to be played back or recorded as follows:
//
// „  Synchronous group:
//
//    „  Play sound resource.
//
//    „  Play sound file.
//
//    „  Record sound resource.
//
//    „  Record sound file.
//
//    „  Speak text string.
//
// „  Asynchronous group:
//
//    „  Start and stop looped sound playback.
//
//    „  Play unlooped sound.
//
//    „  Speak text string.
//
// At startup, the program checks for play-from-disk, sound recording capability, speech
// capability, and multi-channel capability. If these are not available, the relevant 
// buttons are disabled. 
//
// The asynchronous sound sections of the program utilise a special library called
// AsyncSoundLib, which must be included in the CodeWarrior project (AsyncSoundLib68K for
// 680x0 projects and AsyncSoundLibPPC for PowerPC projects).
//
// The program utilises the following resources:
//
// „  'MBAR' and 'MENU' resources (preload, non-purgeable).
//
// „  A 'DLOG' resource and associated 'DITL', 'dlgx', and 'dftb' resources (all
//    purgeable).
//
// „  'CNTL' resources (purgeable) for the controls within the dialog.
//
// „  Three 'snd ' resources, one for synchronous playback (purgeable), one for looped 
//    asynchronous playback (unpurgeable), and one for unlooped asynchronous playback
//    (purgeable).
//
// „  Four 'cicn' resources (purgeable).  Two are used to provide an animated display 
//    which halts  during synchronous playback and continues during asynchronous playback.
//    The remaining two are used by the bevel button controls.
//
// „  Three 'STR#' resources containing error message strings and "speak text" strings
//    (all purgeable). 
//
// „  Two 'ALRT' resources and associated 'DITL', 'alrx', and 'dftb' resources 
//    (all purgeable) for displaying error messages.
//
// „  'hrct' and 'hwin' resources (purgeable) for balloon help.
//
// In addition, the function doPlayFile utilises the file "soundfile.aiff".
//
// Each time is is invoked, the function doRecordResource creates a new 'snd' resource 
// with a unique ID in the application's resource fork.
//
// When first invoked, the function doRecordFile creates the file "test.aiff" in the
// 8chap22_demo folder.  All subsequent record-to-file is to this file.  
//
// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

// ............................................................................. includes

#include <Appearance.h>
#include <ControlDefinitions.h>
#include <Devices.h>
#include <Fonts.h>
#include <Gestalt.h>
#include <Resources.h>
#include <ToolUtils.h>
#include <SoundInput.h>
#include <SpeechSynthesis.h>

// .............................................................................. defines

#define rDialog                 128
#define  iDone                  1
#define  iPlayResource          4
#define  iPlayFile              5
#define  iRecordResource        6
#define  iRecordFile            7
#define  iSpeakTextSync         8
#define  iLoopedSound           9
#define  iUnloopedSound         10  
#define  iSpeakTextAsync        11
#define rPlaySoundResource      8192
#define rLoopedSound            8193
#define rUnloopedSound          8194
#define rSpeechStrings          130
#define rErrorAlert             129
#define rErrorStrings           128
#define  eOpenDialogFail        1
#define  eLoopedSoundSetUp      2
#define  eCannotInitialise      3
#define  eGetResource           4
#define  eNoChannelsAvailable   5
#define  ePlaySound             6
#define  eMemory                7
#define rErrorAlertWithCode     130
#define rErrorStringsWithCode   129
#define  eSndPlay               1    
#define  ePlayFile              2
#define  eSndRecord             3
#define  eWriteResource         4
#define  eRecordFile            5
#define  eSpeakString           6
#define  eSndDoImmediate        7
#define rColourIcon1            128
#define rColourIcon2            129
#define kMaxChannels            8
#define kOutOfChannels          1

// ..................................................................... global variables

Boolean       gDone;
DialogPtr     gDialogPtr;
SInt16        gAppResFileRefNum;
Boolean       gHasSoundPlayDoubBuff;
Boolean       gHasSoundInputDevice;
Boolean       gHasSpeechmanager;
Boolean       gHasMultiChannel;
Boolean       gLoopedSoundOn = false;
SInt32        gLoopedSoundRefNum;
SndChannelPtr gLoopedSoundChannel;

// ......................................................... AsyncSoundLib attention flag

Boolean        gCallAS_CloseChannel = false;

// .................................................................. function prototypes

void        main                    (void);
void        doInitManagers          (void);
void        doCheckSoundEnvironment (void);
void        doInitialiseSoundLib    (void);
Boolean     doLoopedSoundSetUp      (void);
void        eventLoop               (void);
void        doDialogHit             (SInt16);
void        doPlayResource          (void);
void        doPlayFile              (void);
void        doRecordResource        (void);
void        doRecordFile            (void);
void        doSpeakStringSync       (void);
void        doLoopedSoundAsync      (void);
void        doUnloopedSoundAsync    (void);
void        doSpeakStringAsync      (void);
void        doSetUpDialog           (void);
void        doAdjustItems           (void);
void        doErrorAlert            (SInt16);
void        doErrorAlertWithCode    (SInt16,SInt16);

// .................................................... AsyncSoundLib function prototypes

OSErr       AS_Initialise           (Boolean *,SInt16);
OSErr       AS_GetChannel           (SInt32,SndChannelPtr *);
OSErr       AS_PlayID               (SInt16, SInt32 *);
OSErr       AS_PlayHandle           (Handle,SInt32 *);
void        AS_CloseChannel         (void);
void        AS_CloseDown            (void);

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× main

void main(void)
{
  Handle      menubarHdl;
  MenuHandle  menuHdl;

  // ................................................................ initialise managers

  doInitManagers();

  // .......................................................... set up menu bar and menus
  
  menubarHdl = GetNewMBar(128);
  if(menubarHdl == NULL)
    ExitToShell();
  SetMenuBar(menubarHdl);
  DrawMenuBar();

  menuHdl = GetMenuHandle(128);
  if(menuHdl == NULL)
    ExitToShell();
  else
    AppendResMenu(menuHdl,'DRVR');

  // ............................... save reference number of application's resource file 
  
  gAppResFileRefNum  = CurResFile();

  // ........................ check for sound recording equipment and speech capabilities

  doCheckSoundEnvironment();

  // ............................................................. open and set up dialog

  if(!(gDialogPtr = GetNewDialog(rDialog,NULL,(WindowPtr) -1)))
  {
    doErrorAlert(eOpenDialogFail);
    ExitToShell();
  }

  SetPort(gDialogPtr);
  SetDialogDefaultItem(gDialogPtr,kStdOkItemIndex);

  doSetUpDialog();

  // ........................................................... initialise AsychSoundLib

  doInitialiseSoundLib();

  // ................................................................ set up looped sound

  if(gHasMultiChannel)
  {
    if(!doLoopedSoundSetUp()) 
    {
      doErrorAlert(eLoopedSoundSetUp);
      AS_CloseDown();
      ExitToShell();
    }
  }

  // ................................................................... enter event loop

  eventLoop();
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doInitManagers

void  doInitManagers(void)
{
  MaxApplZone();
  MoreMasters();

  InitGraf(&qd.thePort);
  InitFonts();
  InitWindows();
  InitMenus();
  TEInit();
  InitDialogs(NULL);

  InitCursor();  
  FlushEvents(everyEvent,0);

  RegisterAppearanceClient();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doCheckSoundEnvironment

void  doCheckSoundEnvironment(void)
{
  OSErr   osErr;
  SInt32  response;

  osErr = Gestalt(gestaltSoundAttr,&response);

  if(osErr == noErr)
    gHasSoundPlayDoubBuff = BitTst(&response,31 - gestaltSndPlayDoubleBuffer);
  else
    gHasSoundPlayDoubBuff = false;

  if(osErr == noErr)
    gHasSoundInputDevice = BitTst(&response,31 - gestaltHasSoundInputDevice);
  else
    gHasSoundInputDevice = false;

  if(osErr == noErr)
    gHasSpeechmanager = BitTst(&response,31 - gestaltSpeechMgrPresent);
  else
    gHasSpeechmanager = false;

  if(osErr == noErr)
    gHasMultiChannel = BitTst(&response,31 - gestaltMultiChannels);    
  else
    gHasMultiChannel = false;
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doInitialiseSoundLib

void  doInitialiseSoundLib(void)
{
  if(AS_Initialise(&gCallAS_CloseChannel,kMaxChannels) != noErr)
  {
    doErrorAlert(eCannotInitialise);
    ExitToShell();
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doLoopedSoundSetUp

Boolean  doLoopedSoundSetUp(void)
{
  SInt16  error;
  OSErr   osErr;
  Handle  soundHdl;

  error = AS_PlayHandle(NULL,&gLoopedSoundRefNum);
  if(error)
    return(false);
  else 
  {
    error = AS_GetChannel(gLoopedSoundRefNum,&gLoopedSoundChannel);
    if(error)
      return(false);
    
    soundHdl = GetResource('snd ',rLoopedSound);
    if(soundHdl) 
    {
      HLockHi(soundHdl);
      osErr = SndPlay(gLoopedSoundChannel,(SndListHandle) soundHdl,true);
      if(osErr != noErr)
        return(false);
    }
    else
      return(false);
  }
  
  return(true);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× eventLoop

void  eventLoop(void)
{
  Rect        theRect, eraseRect;
  CIconHandle colourIconHdl1;
  CIconHandle colourIconHdl2;
  Boolean     gotEvent;
  EventRecord eventStructure;
  DialogPtr   theDialogPtr;
  SInt16      fontNum, itemHit;
  UInt32      finalTicks;
  WindowPtr   windowPtr;
  SInt16      partCode;

  SetRect(&theRect,267,206,299,238);
  SetRect(&eraseRect,310,206,496,237);
  colourIconHdl1 = GetCIcon(rColourIcon1);
  colourIconHdl2 = GetCIcon(rColourIcon2);

  gDone = false;

  while(!gDone) 
  {
    if(gCallAS_CloseChannel)
    {
      AS_CloseChannel();

      GetFNum("\pGeneva",&fontNum);
      TextFont(fontNum);
      TextSize(10);
      MoveTo(350,226);
      DrawString("\pAS_CloseChannel called");
      Delay(45,&finalTicks);
    }

    gotEvent = WaitNextEvent(everyEvent,&eventStructure,10,NULL);

    if(gotEvent)
    {
      if(IsDialogEvent(&eventStructure))
      {
        if(DialogSelect(&eventStructure,&theDialogPtr,&itemHit))
          doDialogHit(itemHit);
      }
      else
      {
        if(eventStructure.what == mouseDown)
        {
          partCode = FindWindow(eventStructure.where,&windowPtr);
          if(partCode == inDrag)
            DragWindow(windowPtr,eventStructure.where,&qd.screenBits.bounds);
          if(partCode == inMenuBar)
              MenuSelect(eventStructure.where);
        }
      }
    }
    else
    {
      PlotCIcon(&theRect,colourIconHdl1);
      Delay(5,&finalTicks);
      PlotCIcon(&theRect,colourIconHdl2);
      Delay(5,&finalTicks);
      EraseRect(&eraseRect);
    }
  }

  DisposeDialog(gDialogPtr);

  AS_CloseDown();
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doDialogHit

void  doDialogHit(SInt16 item)
{
  switch(item) 
  {
    case iDone:
      gDone = true;
      break;

    case iPlayResource:
      doPlayResource();
      break;

    case iPlayFile:
      doPlayFile();
      break;

    case iRecordResource:
      doRecordResource();
      break;

    case iRecordFile:
      doRecordFile();
      break;

    case iSpeakTextSync:
      doSpeakStringSync();
      break;

    case iLoopedSound:
      doLoopedSoundAsync();
      break;

    case iUnloopedSound:
      doUnloopedSoundAsync();
      break;

    case iSpeakTextAsync:
      doSpeakStringAsync();
      break;
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doPlayResource

void  doPlayResource(void)
{
  SndListHandle sndListHdl;
  SInt16        resErr;
  OSErr         osErr;
  ControlHandle controlHdl;
  
  sndListHdl = (SndListHandle) GetResource('snd ',rPlaySoundResource);
  resErr = ResError();
  if(resErr != noErr)
    doErrorAlert(eGetResource);

  if(sndListHdl != NULL)
  {
    HLock((Handle) sndListHdl);
    osErr = SndPlay(NULL,sndListHdl,false);
    if(osErr != noErr)
      doErrorAlertWithCode(eSndPlay,osErr);
    HUnlock((Handle) sndListHdl);
    ReleaseResource((Handle) sndListHdl);

    GetDialogItemAsControl(gDialogPtr,iPlayResource,&controlHdl);
    SetControlValue(controlHdl,0);
  }
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doPlayFile 

void  doPlayFile(void)
{
  OSErr         osErr;
  FSSpec        fileSysSpec;
  SInt16        fileRefNum;
  ControlHandle controlHdl;
      
  osErr = FSMakeFSSpec(0,0,"\p:soundfile.aiff",&fileSysSpec);
  if(osErr == noErr)
    osErr = FSpOpenDF(&fileSysSpec,fsRdPerm,&fileRefNum);
  if(osErr == noErr)
    SetFPos(fileRefNum,fsFromStart,0);
  if(osErr == noErr)
    osErr = SndStartFilePlay(NULL,fileRefNum,0,20480,NULL,NULL,NULL,false);
  if(osErr != noErr)
    doErrorAlertWithCode(ePlayFile,osErr);      

  FSClose(fileRefNum);

  GetDialogItemAsControl(gDialogPtr,iPlayFile,&controlHdl);
  SetControlValue(controlHdl,0);
}  

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doRecordResource

void  doRecordResource(void)
{
  SInt16        oldResFileRefNum;
  Point         topLeft;
  Handle        soundHdl;
  OSErr         osErr, memErr;
  SInt16        theResourceID, resErr;
  ControlHandle controlHdl;
      
  oldResFileRefNum = CurResFile();
  UseResFile(gAppResFileRefNum);

  topLeft.h = (qd.screenBits.bounds.right / 2) - 156;
  topLeft.v = 150;
  
  soundHdl = NewHandle(25000);
  memErr = MemError();
  if(memErr != noErr)
  {
    doErrorAlert(eMemory);
    return;
  }

  osErr = SndRecord(NULL,topLeft,siBetterQuality,&(SndListHandle) soundHdl);
  if(osErr != noErr && osErr != userCanceledErr)
    doErrorAlertWithCode(eSndRecord,osErr);
  else
  {
    do
    {
      theResourceID = UniqueID('snd ');
    } while(theResourceID <= 8191 && theResourceID >= 0);

    AddResource((Handle) soundHdl,'snd ',theResourceID,"\pTest");
    resErr = ResError();
    if(resErr == noErr)
      UpdateResFile(gAppResFileRefNum);
    resErr = ResError();
    if(resErr != noErr)
      doErrorAlertWithCode(eWriteResource,resErr);
  }

  UseResFile(oldResFileRefNum);

  GetDialogItemAsControl(gDialogPtr,iRecordResource,&controlHdl);
  SetControlValue(controlHdl,0);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doRecordFile

void  doRecordFile(void)
{
  Point         topLeft;
  OSErr         osErr;
  FSSpec        fileSysSpec;
  SInt16        fileRefNum;
  ControlHandle controlHdl;

  topLeft.h = (qd.screenBits.bounds.right / 2) - 156;
  topLeft.v = 150;

  osErr = FSMakeFSSpec(0,0,"\p:test.aiff",&fileSysSpec);
  if(osErr == fnfErr)
    osErr = FSpCreate(&fileSysSpec,'????','AIFF',smSystemScript);
  if(osErr == noErr)
    osErr = FSpOpenDF(&fileSysSpec,fsWrPerm,&fileRefNum);
  if(osErr == noErr)
    SetFPos(fileRefNum,fsFromStart,0);
  if(osErr == noErr)
    osErr = SndRecordToFile(NULL,topLeft,siBetterQuality,fileRefNum);
  if(osErr != noErr && osErr != userCanceledErr)
    doErrorAlertWithCode(eRecordFile,osErr);      

  FSClose(fileRefNum);

  GetDialogItemAsControl(gDialogPtr,iRecordFile,&controlHdl);
  SetControlValue(controlHdl,0);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSpeakStringSync

void  doSpeakStringSync(void)
{
  SInt16        activeChannels;
  Str255        theString;
  OSErr         resErr, osErr;
  ControlHandle controlHdl;

  activeChannels = SpeechBusy();

  GetIndString(theString,rSpeechStrings,1);
  resErr = ResError();
  if(resErr != noErr)
  {
    doErrorAlert(eGetResource);
    return;
  }

  osErr = SpeakString(theString);
  if(osErr != noErr)
    doErrorAlertWithCode(eSpeakString,osErr);
    
  while(SpeechBusy() != activeChannels)
    ;

  GetDialogItemAsControl(gDialogPtr,iSpeakTextSync,&controlHdl);
  SetControlValue(controlHdl,0);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doLoopedSoundAsync

void  doLoopedSoundAsync(void)
{
  SndCommand  sndCommand;
  OSErr       osErr;

  gLoopedSoundOn = !gLoopedSoundOn;

  doAdjustItems();

  sndCommand.param1 = 0;

  if(gLoopedSoundOn) 
  {
    sndCommand.cmd    = freqCmd;
    sndCommand.param2  = 0x3C;
  } 
  else 
  {
    sndCommand.cmd    = quietCmd;
    sndCommand.param2  = 0;
  }

  osErr = SndDoImmediate(gLoopedSoundChannel,&sndCommand);
  if(osErr != noErr)
    doErrorAlertWithCode(eSndDoImmediate,osErr);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doUnloopedSoundAsync

void  doUnloopedSoundAsync(void)
{
  SInt16  error;

  error = AS_PlayID(rUnloopedSound,NULL);
  if(error == kOutOfChannels)
    doErrorAlert(eNoChannelsAvailable);
  else
    if(error != noErr)
      doErrorAlert(ePlaySound);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSpeakStringAsync

void  doSpeakStringAsync(void)
{
  Str255  theString;
  OSErr   resErr, osErr;

  GetIndString(theString,rSpeechStrings,2);
  resErr = ResError();
  if(resErr != noErr)
  {
    doErrorAlert(eGetResource);
    return;
  }
  
  osErr = SpeakString(theString);
  if(osErr != noErr)
    doErrorAlertWithCode(eSpeakString,osErr);
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doSetUpDialog

void  doSetUpDialog(void)
{
  SInt16                        a;
  Point                         offset;
  ControlHandle                 controlHdl;
  ControlButtonGraphicAlignment alignConstant = kControlBevelButtonAlignLeft;
  ControlButtonTextPlacement    placeConstant = kControlBevelButtonPlaceToRightOfGraphic;

  offset.v = 1;
  offset.h = 5;

  for(a=iPlayResource;a<iSpeakTextAsync+1;a++)
  {
    GetDialogItemAsControl(gDialogPtr,a,&controlHdl);
    SetControlData(controlHdl,kControlNoPart,kControlBevelButtonGraphicAlignTag,
                   sizeof(alignConstant),(Ptr) &alignConstant);
    SetControlData(controlHdl,kControlNoPart,kControlBevelButtonGraphicOffsetTag,
                   sizeof(offset),(Ptr) &offset);
    SetControlData(controlHdl,kControlNoPart,kControlBevelButtonTextPlaceTag,
                  sizeof(placeConstant),(Ptr) &placeConstant);
  }

  if(!gHasSoundPlayDoubBuff)
  {
    GetDialogItemAsControl(gDialogPtr,iPlayFile,&controlHdl);
    DeactivateControl(controlHdl);
  }

  if(!gHasSoundInputDevice)
  {
    GetDialogItemAsControl(gDialogPtr,iRecordResource,&controlHdl);
    DeactivateControl(controlHdl);
    GetDialogItemAsControl(gDialogPtr,iRecordFile,&controlHdl);
    DeactivateControl(controlHdl);
  }
  
  if(!gHasSpeechmanager)
  {
    GetDialogItemAsControl(gDialogPtr,iSpeakTextSync,&controlHdl);
    DeactivateControl(controlHdl);
    GetDialogItemAsControl(gDialogPtr,iSpeakTextAsync,&controlHdl);
    DeactivateControl(controlHdl);
  }

  if(!gHasMultiChannel)
  {
    GetDialogItemAsControl(gDialogPtr,iLoopedSound,&controlHdl);
    DeactivateControl(controlHdl);
  }  
}

// ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doAdjustItems

void  doAdjustItems(void)
{
  ControlHandle  controlHdl;
  SInt16        a;

  GetDialogItemAsControl(gDialogPtr,iLoopedSound,&controlHdl);
  if(gLoopedSoundOn)
    SetControlTitle(controlHdl,"\pSwitch Looped Sound Off");
  else
    SetControlTitle(controlHdl,"\pSwitch Looped Sound On");

  for(a=iRecordResource;a<iRecordFile+1;a++)
  {
    GetDialogItemAsControl(gDialogPtr,a,&controlHdl);

    if(gLoopedSoundOn)
      DeactivateControl(controlHdl);
    else
      ActivateControl(controlHdl);
  }    
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doErrorAlert

void doErrorAlert(SInt16 stringIndex)
{
  Str255  errorString;

  GetIndString(errorString,rErrorStrings,stringIndex);
  ParamText(errorString,NULL,NULL,NULL);
  StopAlert(rErrorAlert,NULL);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× doErrorAlertWithCode

void doErrorAlertWithCode(SInt16 stringIndex,SInt16 resultCode)
{
  Str255  errorString, resultCodeString;

  GetIndString(errorString,rErrorStringsWithCode,stringIndex);
  NumToString((SInt32) resultCode,resultCodeString);

  ParamText(errorString,resultCodeString,NULL,NULL);
  StopAlert(rErrorAlertWithCode,NULL);
}

// ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

Demonstration Program Comments

Ensure that the Speech Manager extension is on before running this program.

When this program is run, the user should click on the various buttons in the dialog box
to record and play back sound resources and sound files and to play back the provided
"speak text" strings.  The user should observe the effects of asynchronous and
synchronous playback on the "working man" icon in the image well in the dialog.  The user
should also observe that the text "AS_CloseChannel called" appears briefly in the
secondary group box to the right of the image well when AsynchSoundLib sets the
application's "attention" flag to true, thus causing the application to call the
AsynchSoundLib function AS_CloseChannel.

Note that the doRecordResource function saves recorded sounds as 'snd ' resources with
unique IDs in the resource fork of the application (Sound_68K or Sound_PPC).  In
addition, the doRecordFile function creates a file called "test.aiff" in the directory
containing this application.  When you have finished exploring the recording aspects of
this demonstration, the you may wish to remove the file "test.aiff" and the 'snd '
resources you have created.

#define

rDialog and the following nine constants represent the dialog's resource ID and items. 
The next four constants represent the resource IDs of 'snd ' resources and a 'STR#'
resource containing the "speak text" strings.  The next eighteen constants represent
error 'ALRT' resource IDs, the IDs of "STR#' resources containing error strings, and the
indexes into those "STR#" resources.  The next two constants represent 'cicn' resource
IDs.

kMaxChannels will be used to specify the maximum number of sound channels that
AsynchSoundLib is to open.  kOutOfChannels will be used to determine whether the
AsynchSoundLib function AS_PlayID returns a "no channels available" error.

Global Variables

The application's resource file reference number will be saved to gAppResFileRefNum at
startup.

gHasSoundPlayDoubBuff, gHasSoundInputDevice, gHasSpeechmanager, and gHasMultiChannel will
be set to true if the associated sound capabilities are available, otherwise they will be
set to false.

gLoopedSoundOn will be toggled between true and false by successive presses of the
"Switch Looped Sound On/Off" bevel button.  gLoopedSoundRefNum will be assigned the
reference number returned by a call to the AsynchSoundLib function AS_PlayHandle. 
gLoopedSoundChannel will be assigned the pointer to the sound channel structure returned
by a call to the AsynchSoundLib function AS_GetChannel.

gCallAS_CloseChannel is the application's "attention" flag.  This will be set to true by
AsynchSoundLib when a sound played asynchronously has stopped playing.

main

CurResFile saves the reference number of the application's resource file.  The call to
doCheckSoundEnvironment checks the capability of the sound environment and sets global
variables accordingly.

doInitialiseSoundLib is called to initialise the AsynchSoundLib library.  

If multi-channel playback is available, the application-defined function
doLoopedSoundSetUp is called to set up the looped sound playback.  If this call is not
successful, an error alert is displayed, the AsynchSoundLib function AS_CloseDown is
called and the program terminates.

This block means that, on machines without multi-channel playback capability, the
program has opted to defeat the continuous looped sound playback and make the 
single channel available for the other playback options represented by the 
buttons in the dialog.  The program could be readily modified to reverse this
situation and allow the user to make the single channel available to the 
continuous looped sound only.

doCheckSoundEnvironment

doCheckSoundEnvironment checks for play-from-disk capability, recording capability,
speech capability, and multi-channel playback capability, and sets the associated global
variables accordingly.

doInitialiseSoundLib

doInitialiseSoundLib initialises the AsynchSoundLib library.  More specifically, it calls
the AsynchSoundLib function AS_Initialise and passes to AsynchSoundLib the address of the
application's "attention" flag (gAS_CloseChannel), together with the requested number of
channels.

If AS_Initialise returns a non-zero value, an error alert is displayed and the program
terminates.

doLoopedSoundSetUp

doLoopedSoundSetUp gets a channel for the looped sound, loads the 'snd ' resource
containing the looped sound, and calls SndPlay.

First, the AsynchSoundLib function AS_PlayHandle is called with NULL passed in the first
parameter.  (This causes AS_PlayHandle to open a sound channel but not call SndPlay.) 
The second parameter is the address of a global variable which will receive the reference
number associated with the channel opened by this call to AS_PlayHandle.

If the call to AS_PlayHandle is successful, a call is made to the AsynchSoundLib function
AS_GetChannel, passing the reference number returned by AS_PlayHandle in the first
parameter and receiving a pointer to the sound channel in the second parameter.

If the call to AS_GetChannel is successful, GetResource attempts to load the specified
'snd ' resource.  If the resource is loaded successfully, it is first moved as high in
the application heap as possible and locked there.  SndPlay is then called with true
passed in the third parameter, indicating that asynchronous playback is required of the
sound passed in the second parameter on the channel passed in the first parameter.

The 'snd ' resource being used contains one command only (soundCmd).  In the
standard sound header, the loopStart field contains 0 and the loopEnd field
contains 24199.  (The sound length is 24200 frames.)  Since the soundCmd command
may only be used with non-compressed sampled-sound data, the sampled sound data
in the resource is not compressed.

SndPlay causes all commands and data contained in the sound handle to be sent to the
channel.  Since the single command in the 'snd ' resource being used is soundCmd (install
a sampled sound as a voice in a channel) and not bufferCmd (play a sampled sound),
nothing is heard at this point.  (If the command in the resource was bufferCmd, the sound
would play once at this point.)

If all four calls in doLoopedSoundSetUp are successful, true is returned.  Otherwise,
false is returned and the program terminates.

eventLoop

Within the event loop, the "attention" flag required by AsynchSoundLib is checked.  If
AsynchSoundLib has set it to true, the AsynchSoundLib function AS_CloseChannel is called
to free up the relevant ASStructure, close the relevant sound channel, and clear the
"attention" flag.  In addition, some text is drawn in the group box to the right of the
image well to indicate to the user that AS_CloseChannel has just been called.

If WaitNextEvent retrieves an event other than a NULL event, IsDialogEvent is called to
determine whether the event belongs to the dialog.  If so, DialogSelect is called to
determine whether one of the dialog's buttons was clicked.  If so, the
application-defined function doDialogHit is called to further process the item hit.  If
the event does not belong to the dialog, the else block supports dragging of the dialog
and choosing Show/Hide Balloons from the Help menu.

If a null event was returned by WaitNextEvent, the two frames of "working man" animation
are drawn within the image well, separated by five ticks, and the area in which
"AS_CloseChannel called" may have been drawn is erased.

When gDone is set to true, the event loop exits, the dialog is disposed of, and the
AsynchSoundLib function AS_CloseDown is called to stop all current playback, close open
sound channels, and dispose of the associated ASStructures.

doDialogHit

doDialogHit switches according to the received item number and calls the appropriate
application-defined function to further process the item hit event.

doPlayResource

doPlayResource is the first of the synchronous playback functions.  It uses SndPlay to
play a specified 'snd ' resource.

GetResource attempts to load the resource.  If the subsequent call to ResError indicates
an error, an error alert is presented.

If the load was successful, the sound handle is locked prior to a call to SndPlay.  Since
NULL is passed in the first parameter of the SndPlay call, SndPlay automatically
allocates a sound channel to play the sound and deallocates the channel when the playback
is complete.  false passed in the third parameter specifies that the playback is to be
synchronous.

The 2174-byte 'snd ' resource being used contains one command only (bufferCmd).
The compressed sound header indicates MACE 3:1 compression.  The loopStart
field of the compressed sound header contains 6270 and the loopEnd field 
contains 6271.  (The sound length is 6270 frames.)  The 8-bit mono sound was
sampled at 22kHz.

SndPlay causes all commands and data contained in the sound handle to be sent to the
channel.  Since there is a bufferCmd command in the 'snd ' resource, the sound is played.

If SndPlay returns an error, an error alert is presented.

When SndPlay returns, HUnlock unlocks the sound handle and ReleaseResource releases the
resource.

doPlayFile

doPlayFile uses SndStartFilePlay to play a specified sound file.

FSMakeFSSpec converts the directory specification shown into an FSSpec structure.  The
pointer to the FSSpec structure returned by FSMakeFSSpec is passed in the first parameter
of a call to FSpOpenDF.  FSpOpenDF opens the file's data fork and receives the file
reference number in its third parameter.  SetFPos positions the file mark to the
beginning of the file.

The file reference number is passed in the second parameter of the call to
SndStartFilePlay.  The parameters passed to SndStartFilePlay are as follows:

*   NULL in the chan parameter causes SndStartPlay to allocate a sound channel itself.

*   fileRefNum in the fRefNum parameter specifies the file reference number of the file
    to be played.

*   resNum is 0 because a file is being played, not a 'snd ' resource.

*   20480 in the bufferSize parameter means the number of bytes to be allocated for
    input buffering.

*   NULL in the theBuffer parameter causes the Sound Manager to internally allocate
    two relocatable blocks, each of which is half the size of bufferSize.

*   NULL in the theSelection parameter means the entire sound will be played.

*   NULL in the theCompletion parameter means that there is no completion function
    to be called when the file has finished playing.

*   false in the async parameter means that playback is to be synchronous.

If an error is detected along the way, doErrorAlert presents an error alert.

FSClose closes the file.

The MACE 6:1 AIFF-C file being used was sampled at 22kHz as 8-bit mono sound.
Because of the high compression, the sound quality is poor.

doRecordResource

doRecordResource uses SndRecord to record a sound synchronously and then saves the sound
in a 'snd ' resource.

CurResFile saves the current resource file reference number and UseResFile sets the
application's resource fork as the current resource file.  (The 'snd ' resource will be
saved to the resource fork of the application file (Sound_68K or Sound_PPC).)

The next two lines establish the location for the top left corner of the sound recording
dialog.

NewHandle creates a relocatable block.  The address of the handle will be passed as the
fourth parameter of the SndRecord call.  The size of this block determines the recording
time available.  (If NULL is passed as the fourth parameter of a SndRecord call, the
Sound Manager allocates the largest block possible in the application's heap.)  If
NewHandle cannot allocate the block, an error alert is presented and the function
returns.

SndRecord opens the sound recording dialog and handles all user interaction until the
user clicks the Cancel or Save button.  Note that the second parameter of the SndRecord
call establishes the location for the top left corner of the sound recording dialog and
that the third parameter specifies 22kHz, mono, 3:1 compression.

When the user clicks the Save button, the handle is resized automatically.  If the user
clicks the Cancel button, SndRecord returns userCanceledErr.  If SndRecord returns an
error other than userCanceledErr, an error alert is presented and the function returns.

The relocatable block allocated by NewHandle, and resized as appropriate by SndPlay, has
the structure of a 'snd ' resource, but its handle is not a handle to an existing
resource.  To save the recorded sound as a 'snd ' resource in the application's resource
fork, the do/while loop first finds an acceptable unique resource ID for the resource. 
(For the System file, resource IDs for 'snd ' resources in the range 0 to 8191 are
reserved for use by Apple Computer, Inc.  Avoiding those IDs in this demonstration is not
strictly necessary, since there is no intention to move those resources to the System
file.).  

The call to AddResource at causes the Resource Manager to regard the relocatable block
containing the sound as a 'snd ' resource.  If the call is successful, UpdateResFile
writes the changed resource map and the 'snd ' resource to disk.  If an error occurs, an
error alert is presented.

UseResFile restores the previously saved resource file as the current resource file.

Note that, ordinarily, you should not record to your application's resource fork because
applications which record to their own resource fork cannot be used over networks.

doRecordFile

doRecordFile uses SndRecordToFile to record a sound synchronously to a file.

The first two lines establish the location for the top left corner of the sound recording
dialog.

FSMakeFSSpec converts the directory specification passed in its third parameter into an
FSSpec structure.  If FSMakeFSSpec returns fnfErr (file not found), FSpCreate creates a
new file of type 'AIFF'.  FSpOpenDF opens the file's data fork and SetFPos positions the
file mark to the beginning of the file.

SndRecordToFile opens the sound recording dialog and handles all user interaction until
the user clicks the Cancel or Save button.  Note that the second parameter of the
SndRecord call establishes the location for the top left corner of the sound recording
dialog, that the third parameter specifies 22kHz, mono, 3:1 compression, and that the
fourth parameter specifies the file reference number of the file to record to.

When SndRecordToFile returns, the file will contain the recorded audio data.  Since
compression was specified, the file will be in AIFF-C format.

If the user clicks the Cancel button, SndRecordToFile returns userCanceledErr.  If an
error occurs along the way and it is not userCanceledErr, an error alert is presented.
FSClose closes the file.

doSpeakStringSync

doSpeakStringSync uses SpeakString to speak a specified string resource and takes
measures to cause the speech to be generated in a psuedo-synchronous manner.

The speech that SpeakString generates is asynchronous, that is, control returns to the
application before SpeakString finishes speaking the string.  In this function,
SpeechBusy is used to cause the speech activity to be synchronous so far as the function
as a whole is concerned.  That is, doSpeakStringSync will not return until the speech
activity is complete.

As a first step, the first line saves the number of speech channels that are active
immediately before the call to SpeakString.

GetIndString loads the first string from the specified 'STR#' resource.  If an error
occurs, an error alert is presented and the function returns.

SpeakString, which automatically allocates a speech channel, is called to speak the
string.  If SpeakString returns an error, an error alert is presented.

Although SpeakString returns control to the application immediately it starts generating
the speech, the speech channel it opens remains open until the speech concludes.  While
the speech continues, the number of speech channels open will be one more that the number
saved at the first line.  Accordingly, the while loop continues until the number of open
speech channels is equal to the number saved at the first line.  Then, and only then,
does doSpeakStringSync exit.

doLoopedSoundAsync

doLoopedSoundAsync is the first of the asynchronous playback functions.  It sends sound
commands to the sound channel opened by the application-defined function
doLoopedSoundSetUp, and on which doLoopedSoundSetUp has already installed a voice.

The first line toggles the Boolean global variable gLoopedSoundOn to the opposite state.

The next line calls an application-defined function which, depending on the value in
gLoopedSoundOn, toggles the button title between "Switch Looped Sound On" and Switch
Looped Sound Off" and toggles the "Record Sound Resource" and "Record Sound File" buttons
between the deactivated and activated states.

Depending on the value in gLoopedSoundOn, the if/else block will be sending either the
freqCmd command or the quietCmd command to the channel on which the looped sound is
installed.  In both of these commands, param1 should be set to 0.

If the value in gLoopedSoundOn is true, the cmd field of a sound command structure is
assigned freqCmd and the param2 field is assigned a value (60 decimal) which equates to
middle C.  (The freqCmd command changes the frequency (or pitch) of a sound.  Also, if no
sound is currently playing, freqCmd causes the Sound Manager to begin playing at the
specified frequency.  If, however, no voice is installed in the channel, no sound is
produced.  (A voice was installed in the channel to which the command will be sent by the
application-defined function doLoopedSoundSetUp.)

If the value in gLoopedSoundOn is false, the cmd field of a sound command structure is
assigned quietCmd and the param2 field is assigned 0.  (The quietCmd command stops the
sound that is currently playing, and should be sent using SndDoImmediate.)

SndDoImmediate is called to send the command specified in the second parameter to the
sound channel specified in the first parameter.  If SndDoImmediate returns an error, an
error alert is presented.

doUnloopedSoundAsync

doUnloopedSoundAsync uses the AsynchSoundLib function AS_PlayID to play a 'snd ' resource
asynchronously.

AS_PlayID is called to play the 'snd ' resource specified in the first parameter.  Since
no further control over the playback is required, NULL is passed in the second parameter. 
(Recall that, if you pass a pointer to a variable in the second parameter, AS_PlayID
returns a reference number in that parameter.  That reference number may be used to gain
more control over the playback process.  If you simply want to trigger a sound and let it
to run to completion, you pass NULL in the second parameter, in which case a reference
number is not returned by AS_PlayID.)

If AS_PlayID returns the "no channels currently available" error, an error alert is
presented advising of that specific condition.  If any other error is returned, a more
generalised error alert is presented.

When the sound has finished playing, ASynchSoundLib advises the application by setting
the application's "attention" flag to true.  Recall that this will cause the
AsynchSoundLib function AS_CloseChannel to be called to free up the relevant ASStructure,
close the relevant sound channel, clear the "attention" flag, and draw some text in the
group box to the right of the image well to indicate to the user that AS_CloseChannel has
just been called.

The 701-byte 'snd ' resource being used contains one command only (bufferCmd). 
The compressed sound header indicates MACE 6:1 compression.  The loopStart field of the
compressed sound header contains 3704 and the loopEnd field contains 3705.  The 8-bit
mono sound was sampled at 22kHz.

doSpeakStringAsync

doSpeakStringAsync is identical to the function doSpeakStringSync except that, in this
function, SpeechBusy is not used to delay the function returning until the speech
activity spawned by SpeakString has run its course.

doSetUpDialog

doSetUpDialog sets up the graphic alignment, graphic offset, and text placement for the
bevel buttons.  It then deactivates any bevel buttons relating to sound features not
available on the machine on which the program is running.

doAdjustItems

doAdjustItems toggles the "Switch looped Sound" button between on and off, and the
"Record Sound Resource" and "Record Sound File" buttons between activated and
deactivated, according to the value in gLoopedSoundOn.
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasnā€™t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, donā€™t remind me I could currently buy him this month Iā€™m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly ā€œborrowedā€ many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »

Price Scanner via MacPrices.net

New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Appleā€™s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
Weā€™ve updated our iPhone Price Tracker with the latest carrier deals on Appleā€™s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobileā€™s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobileā€™s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13ā€³ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Appleā€™s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16ā€³ and 14ā€³ MacBook Pros along with 13ā€³ and 15ā€³ MacBook... Read more
Every model of Appleā€™s 13-inch M3 MacBook Air...
Best Buy has Apple 13ā€³ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13ā€³ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9ā€³ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Appleā€™s standard one-year warranty... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
ā€¦relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.