Copyright (c) Hyperion Entertainment and contributors.

AHI Device

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

About AHI

AHI is an audio system that provides support for a wide range of audio hardware. Compared with the Audio Device, AHI offers more functionality, such as audio playback and recording from a user selected device.

Definitions

Terms used in the following discussions may be unfamiliar. Some of the more important ones are defined below.

Sample
A sample is one binary number, representing the amplitude at a fixed point in time. A sample is often stored as an 8 bit signed integer, a 16 bit signed integer, a 32 bit floating point number etc. AHI supports only integers.
Sample frame
In mono environment, a sample frame is the same as a sample. In stereo environment, a sample frame is a tuple of two samples. The first member is for the left channel and the second member is for the right channel.
Sound
Many sample frames stored in sequence as an array can be called a sound. A sound is, however, not limited to being formed by samples. It can also be parameters to an analog synth or a MIDI instrument, or be white noise. AHI supports only sounds formed by samples.

AHI Device

AHI device has two APIs: a library-like function interface for low-level access and device interface for high-level access.

AHI Device Commands and Functions

Command Command Operation
CMD_FLUSH Abort all current requests, both active and waiting, even other programs requests!
CMD_READ Read raw samples from audio input.
CMD_RESET Restore device to a known state.
CMD_START Start device processing (like ^Q).
CMD_STOP Stop device processing (like ^S).
CMD_WRITE Write raw samples to audio output.
NSCMD_DEVICEQUERY Query the device for its capabilities.

Device Interface

The AHI device operates like the other Amiga I/O devices. To play or record sound, you first open the AHI device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.

AHI device commands use an extended I/O request block named AHIRequest to send commands to the AHI device. This is the standard IOStdReq block with some extra fields added at the end.

struct AHIRequest
{
    struct IOStdReq ahir_Std;       /* Standard IO request     */
    UWORD  ahir_Version;            /* Needed version          */
    UWORD  ahir_Pad1;
    ULONG  ahir_Private[2];         /* Hands off!              */
    ULONG  ahir_Type;               /* Sample format           */
    ULONG  ahir_Frequency;          /* Sample/Record frequency */
    Fixed  ahir_Volume;             /* Sample volume           */
    Fixed  ahir_Position;           /* Stereo position         */
    struct AHIRequest *ahir_Link;   /* For double buffering    */
};

Opening the AHI Device

Before you can use AHI device, it must be opened with an OpenDevice() call. Four primary steps are required to open AHI device:

  • Create a message port using CreateMsgPort().
  • Create an extended I/O request structure of type AHIRequest using CreateIORequest().
  • Specify which version of the device you need. The lowest supported version is 4.
  • Open ahi.device unit AHI_DEFAULT_UNIT or any other unit the user has specified, for example through UNIT tooltype.
struct MsgPort    *AHImp;
struct AHIRequest *AHIio;
 
if((AHImp = IExec->CreateMsgPort()))
{
  if((AHIio = (struct AHIRequest *) IExec->CreateIORequest(AHImp,sizeof(struct AHIRequest))))
  {
    AHIio->ahir_Version = 4;
    if(!(IExec->OpenDevice(AHINAME, AHI_DEFAULT_UNIT,(struct IORequest *) AHIio,NULL)))
    {

Reading from the AHI Device

You read from the AHI device by passing an AHIRequest to the device with CMD_READ set in the io_Command, the number of bytes to be read set in io_Length, the address of the read buffer set in io_Data, the desired sample format set in ahir_Type, and the desired sample frequency set in ahir_Frequency. The first read command in a sequence should also have io_Offset set to 0. io_Length must be an even multiple of the sample frame size.

  #define FREQUENCY  8000
  #define TYPE       AHIST_M8S
  #define BUFFERSIZE 20000
 
  BYTE buffer[BUFFERSIZE];
 
  AHIio->ahir_Std.io_Command = CMD_READ;
  AHIio->ahir_Std.io_Length  = BUFFERSIZE;
  AHIio->ahir_Std.io_Data    = buffer;
  AHIio->ahir_Type           = TYPE;
  AHIio->ahir_Frequency      = FREQUENCY;
  AHIio->ahir_Std.io_Offset  = 0;
 
  IExec->SendIO((struct IORequest *) AHIio);

Double Buffering

To do double buffering you need two buffers: buffer1 and buffer2. Then do as follows:

  • Fill the first buffer (buffer1) with DoIO() and io_Offset set to 0.
  • Start filling the second buffer (buffer2) with SendIO() using the same I/O request, but do not clear io_Offset.
  • Process the first buffer and wait until the I/O request is finished.
  • Start over with SendIO() on the first buffer.

Distortion

The samples will automatically be converted to the sample format set in ahir_Type and to the sample frequency set in ahir_Frequency. Because it is quite unlikely that you ask for same sample frequency the user has chosen in the AHI Preferences Editor, chances that the quality is lower than expected are pretty high. The worst problem is probably the anti-aliasing filter before the A/D converter. If the user has selected a higher sampling/mixing frequency than you request, the signal will be distorted according to the Nyquist sampling theorem. If, on the other hand, the user has selected a lower sampling/mixing frequency than you request, the signal will not be distorted but rather bandlimited more than necessary.

Writing to the AHI Device

You write to the device by passing AHIRequest to the device with CMD_WRITE set in io_Command, the precedence in io_Message.mn_Node.ln_Pri, the number of bytes to be written in io_Length, the address of the writer buffer set in io_Data, the sample format set in ahir_Type, the desired sample frequency set in ahir_Frequency, the desired volume set in ahir_Volume, and the desired stereo position set in ahir_Position. Unless you are doing double buffering, ahir_Link should be set to NULL. io_Length must be even multiple of the sample frame size.

Double Buffering

To do double buffering, you need two I/O requests: AHIio and AHIio2. Create the second one by making a copy of the request you used in OpenDevice(). Then do as follows:

  • Start the first request (AHIio) with SendIO().
  • Set ahir_Link in the second request (AHIio2) to the address of the first request (AHIio) and SendIO() it.
  • Wait for the first request (AHIio) to finish.
  • When finished, fill the first buffer again and repeat, but this time with ahir_Link of the first request (AHIio) set to the address of the second I/O request (AHIio2).

Distortion

The problems with aliasing are present but not as obvious as with reading. Just make sure your data is bandlimited correctly and do not play samples at a lower frequency than they were recorded.

Multiple Sounds at the Same Time

If you want to play several sounds at the same time, just make a new copy of the I/O request you used in OpenDevice() and CMD_WRITE it.

The number of available channels is set in the AHI Preferences editor by the user. If too many requests are sent to the device, the one with the lowest precedence will be muted. When a request is finished, the muted request with the highest precedence will be played.

Note that all muted requests continue to play silently, so the programmer will not have to worry whether there are enough channels or not.

Suggested Precedences for Channel Allocation

The precedences to use depend on what kind of sound you are playing. The recommended precedences are the same as for Audio Device.

Precedences Type of Sound
127 Unstoppable. Sounds first allocated at lower precedencies, then set to this highest level.
90 – 100 Emergencies. Alert, urgent situation that requires immediate action.
80 – 90 Annunciators. Attention, bell (CTRL-G).
75 Speech. Synthesized or recorded speech.
50 – 70 Sonic cues. Sounds that provide information that is not provided by graphics. Only the beginning of each sound (enough to recognize it) should be at this level; the rest should be set to sound effects level.
-50 – 50 Music program. Musical notes in a music-oriented program. The higher levels should be used for the attack portions of each note.
-70 – -50 Sound effects. Sounds used in conjunction with graphics. More important sounds should use higher levels.
-100 – -80 Background. Theme music and restartable background sounds.
-128 Silence. Lowest level (freeing the channel completely is preferred).

Some things are differend in AHI compared to Audio Device:

  1. There is no way to change the precedence of a playing sound, so the precedences should be set from the beginning.
  2. It is not recommended to use the device interface to play music. However, playing an audio stream from a CD or disk comes very close.
  3. There are no channels to free in AHI since they are dynamically allocated by the device.

Closing the AHI Device

An OpenDevice() must eventually be matched by a call to CloseDevice(). All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO():

IExec->AbortIO((struct IORequest *)AHIio);     /* Abort any pending requests */
IExec->WaitPort(AHImp);                        /* Wait for abort message     */
IExec->GetMsg(AHImp);                          /* Get abort message          */
IExec->CloseDevice((struct IORequest *)AHIio);

Function Interface

Opening the AHI Device for Low-level Access

Obtaining the Hardware

Declaring Sounds

Playing Sounds

Closing the AHI Device

Examples

Playback Example

/*
** This program uses the device interface to play a sampled sound.
** The input is read from THE DEFAULT INPUT, make sure you
** start it with "PlayTest pri < mysample.raw" !
** Where pri is a number from -128 to +127 (may be omitted)
** The sample should be 8 bit signed, mono (see TYPE).
**
** PLEASE NOTE that earlier versions of this example contained a bug
** that sometimes DeleteIORequest'ed a pointer that was AllocMem'ed!
*/
 
#include <devices/ahi.h>
#include <dos/dosasl.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/ahi.h>
#include <stdlib.h>
 
#define FREQUENCY  8000
#define TYPE       AHIST_M8S
#define BUFFERSIZE 20000
 
struct MsgPort    *AHImp     = NULL;
struct AHIRequest *AHIios[2] = {NULL,NULL};
struct AHIRequest *AHIio     = NULL;
APTR               AHIiocopy = NULL;
BYTE               AHIDevice = -1;
 
BYTE buffer1[BUFFERSIZE];
BYTE buffer2[BUFFERSIZE];
 
void cleanup(LONG rc)
{
  if(!AHIDevice)
    CloseDevice((struct IORequest *)AHIio);
  DeleteIORequest((struct IORequest *)AHIio);
  FreeMem(AHIiocopy,sizeof(struct AHIRequest));
  DeleteMsgPort(AHImp);
  exit(rc);
}
 
int main(int argc, char *argv[])
{
  BYTE *p1=buffer1,*p2=buffer2;
  void *tmp;
  ULONG signals,length;
  struct AHIRequest *link = NULL;
  LONG priority = 0;
  BYTE pri;
 
  if(argc == 2)
  {
    StrToLong(argv[1], &priority);
  }
  pri = priority;
  Printf("Sound priority: %ld\n", pri);
 
  if((AHImp=CreateMsgPort()) != NULL) {
    if((AHIio=(struct AHIRequest *)CreateIORequest(AHImp,sizeof(struct AHIRequest))) != NULL) {
      AHIio->ahir_Version = 4;
      AHIDevice=OpenDevice(AHINAME,0,(struct IORequest *)AHIio,0);
    }
  }
 
  if(AHIDevice) {
    Printf("Unable to open %s/0 version 4\n",AHINAME);
    cleanup(RETURN_FAIL);
  }
 
// Make a copy of the request (for double buffering)
  AHIiocopy = AllocMem(sizeof(struct AHIRequest), MEMF_ANY);
  if(! AHIiocopy) {
    cleanup(RETURN_FAIL);
  }
  CopyMem(AHIio, AHIiocopy, sizeof(struct AHIRequest));
  AHIios[0]=AHIio;
  AHIios[1]=AHIiocopy;
 
  SetIoErr(0);
 
  for(;;) {
 
// Fill buffer
    length = Read(Input(),p1,BUFFERSIZE);
 
// Play buffer
    AHIios[0]->ahir_Std.io_Message.mn_Node.ln_Pri = pri;
    AHIios[0]->ahir_Std.io_Command  = CMD_WRITE;
    AHIios[0]->ahir_Std.io_Data     = p1;
    AHIios[0]->ahir_Std.io_Length   = length;
    AHIios[0]->ahir_Std.io_Offset   = 0;
    AHIios[0]->ahir_Frequency       = FREQUENCY;
    AHIios[0]->ahir_Type            = TYPE;
    AHIios[0]->ahir_Volume          = 0x10000;          // Full volume
    AHIios[0]->ahir_Position        = 0x8000;           // Centered
    AHIios[0]->ahir_Link            = link;
    SendIO((struct IORequest *) AHIios[0]);
 
    if(link) {
 
// Wait until the last buffer is finished (== the new buffer is started)
      signals=Wait(SIGBREAKF_CTRL_C | (1L << AHImp->mp_SigBit));
 
// Check for Ctrl-C and abort if pressed
      if(signals & SIGBREAKF_CTRL_C) {
        SetIoErr(ERROR_BREAK);
        break;
      }
 
// Remove the reply and abort on error
      if(WaitIO((struct IORequest *) link)) {
        SetIoErr(ERROR_WRITE_PROTECTED);
        break;
      }
    }
 
// Check for end-of-sound, and wait until it is finished before aborting
    if(length != BUFFERSIZE) {
      WaitIO((struct IORequest *) AHIios[0]);
      break;
    }
 
    link = AHIios[0];
 
// Swap buffer and request pointers, and restart
    tmp    = p1;
    p1     = p2;
    p2     = tmp;
 
    tmp    = AHIios[0];
    AHIios[0] = AHIios[1];
    AHIios[1] = tmp;
  }
 
 
// Abort any pending iorequests
  AbortIO((struct IORequest *) AHIios[0]);
  WaitIO((struct IORequest *) AHIios[0]);
 
  if(link) { // Only if the second request was started
    AbortIO((struct IORequest *) AHIios[1]);
    WaitIO((struct IORequest *) AHIios[1]);
  }
 
  if(IoErr()) {
    PrintFault(IoErr(), argv[0] );
    cleanup(RETURN_ERROR);
  }
 
  cleanup(RETURN_OK);
  return RETURN_OK; // Make compiler happy
}

Double Buffered Sound Example

/* Simple sample player for AHI using the low-level API
   with double buffered AHIST_DYNAMICSAMPLE sounds.
 
   Usage: DoubleBuffer < [raw sample file]
 
   the file must be in mono 16 bit signed big endian format sampled
   in 17640 Hz.
 
   This software is Public Domain. */
 
#include <devices/ahi.h>
#include <exec/exec.h>
#include <proto/ahi.h>
#include <proto/dos.h>
#include <proto/exec.h>
 
#define EQ ==
#define MINBUFFLEN 10000
 
struct Library    *AHIBase;
struct MsgPort    *AHImp=NULL;
struct AHIRequest *AHIio=NULL;
BYTE               AHIDevice=-1;
 
BYTE signal=-1;
 
struct AHIAudioModeRequester *req=NULL;
struct AHIAudioCtrl *actrl=NULL;
 
BOOL DBflag=FALSE;  // double buffer flag
ULONG BufferLen=NULL;
 
struct AHISampleInfo Sample0 =
{
  AHIST_M16S,
  NULL,
  NULL,
};
 
struct AHISampleInfo Sample1 =
{
  AHIST_M16S,
  NULL,
  NULL,
};
 
__asm __saveds ULONG SoundFunc(register __a0 struct Hook *hook,
    register __a2 struct AHIAudioCtrl *actrl,
    register __a1 struct AHISoundMessage *smsg)
{
  if(DBflag = !DBflag) // Flip and test
    AHI_SetSound(0,1,0,0,actrl,NULL);
  else
    AHI_SetSound(0,0,0,0,actrl,NULL);
  Signal(actrl->ahiac_UserData,(1L<<signal));
  return NULL;
}
 
struct Hook SoundHook =
{
  0,0,
  SoundFunc,
  NULL,
  NULL,
};
 
 
int main()
{
  ULONG mixfreq,playsamples,readsamples,signals,i;
 
  if(AHImp=CreateMsgPort())
  {
    if(AHIio=(struct AHIRequest *)CreateIORequest(AHImp,sizeof(struct AHIRequest)))
    {
      AHIio->ahir_Version = 4;  // Open at least version 4 of 'ahi.device'.
      if(!(AHIDevice=OpenDevice(AHINAME,AHI_NO_UNIT,(struct IORequest *)AHIio,NULL)))
      {
        AHIBase=(struct Library *)AHIio->ahir_Std.io_Device;
 
        if(req=AHI_AllocAudioRequest(
            AHIR_PubScreenName,"",
            AHIR_TitleText,"Select a mode and rate",
            AHIR_InitialMixFreq,17640,
            AHIR_DoMixFreq,TRUE,
            TAG_DONE))
        {
          if(AHI_AudioRequest(req,TAG_DONE))
          {
            if(actrl=AHI_AllocAudio(
                AHIA_AudioID,req->ahiam_AudioID,
                AHIA_MixFreq,req->ahiam_MixFreq,
                AHIA_Channels,1,
                AHIA_Sounds,2,
                AHIA_SoundFunc,&SoundHook,
                AHIA_UserData,FindTask(NULL),
                TAG_DONE))
            {
              AHI_GetAudioAttrs(AHI_INVALID_ID,actrl,
                  AHIDB_MaxPlaySamples,&playsamples,
                  TAG_DONE);
              AHI_ControlAudio(actrl,
                  AHIC_MixFreq_Query,&mixfreq,
                  TAG_DONE);
              BufferLen=playsamples*17640/mixfreq;
              if (BufferLen<MINBUFFLEN)
                BufferLen=MINBUFFLEN;
 
              Sample0.ahisi_Length=BufferLen;
              Sample1.ahisi_Length=BufferLen;
              Sample0.ahisi_Address=AllocVec(BufferLen*2,MEMF_PUBLIC|MEMF_CLEAR);
              Sample1.ahisi_Address=AllocVec(BufferLen*2,MEMF_PUBLIC|MEMF_CLEAR);
 
              if(Sample0.ahisi_Address && Sample1.ahisi_Address)
              {
                if((!(AHI_LoadSound(0,AHIST_DYNAMICSAMPLE,&Sample0,actrl))) &&
                   (!(AHI_LoadSound(1,AHIST_DYNAMICSAMPLE,&Sample1,actrl))))
                {
                  if((signal=AllocSignal(-1)) != -1)
                  {
                    if(!(AHI_ControlAudio(actrl,
                        AHIC_Play,TRUE,
                        TAG_DONE)))
                    {
// Everything is set up now. Let's load the buffers and start rockin'...
                      Read(Input(),Sample0.ahisi_Address,BufferLen*2);
 
// The new AHI_PlayA() function is demonstrated...
                      AHI_Play(actrl,
                        AHIP_BeginChannel,0,
                        AHIP_Freq,17640,
                        AHIP_Vol,0x10000L,
                        AHIP_Pan,0x8000L,
                        AHIP_Sound,0,
                        AHIP_Offset,0,
                        AHIP_Length,0,
                        AHIP_EndChannel,NULL,
                        TAG_DONE);
/* These functions were available in V2, too.
                      AHI_SetFreq(0,17640,actrl,AHISF_IMM);
                      AHI_SetVol(0,0x10000L,0x8000L,actrl,AHISF_IMM);
                      AHI_SetSound(0,0,0,0,actrl,AHISF_IMM);
*/
                      for(;;)
                      {
                        signals=Wait((1L<<signal) | SIGBREAKF_CTRL_C);
                        if(signals & SIGBREAKF_CTRL_C)
                          break;
 
                        if(DBflag)
                        {
                          readsamples=Read(Input(),Sample1.ahisi_Address,BufferLen*2)/2;
                          if(readsamples<BufferLen)
                          {
                            // Clear rest of buffer
                            for(i=readsamples;i<BufferLen;i++)
                              ((WORD *)Sample1.ahisi_Address)[i]=0;
                            Wait(1L<<signal);
                            // Clear other buffer
                            for(i=0;i<BufferLen;i++)
                              ((WORD *)Sample0.ahisi_Address)[i]=0;
                            break;
                          }
                        }
                        else
                        {
                          readsamples=Read(Input(),Sample0.ahisi_Address,BufferLen*2)/2;
                          if(readsamples<BufferLen)
                          {
                            // Clear rest of buffer
                            for(i=readsamples;i<BufferLen;i++)
                              ((WORD *)Sample0.ahisi_Address)[i]=0;
                            Wait(1L<<signal);
                            // Clear other buffer
                            for(i=0;i<BufferLen;i++)
                              ((WORD *)Sample1.ahisi_Address)[i]=0;
                            break;
                          }
                        }
 
 
                      }
                      if(signals & SIGBREAKF_CTRL_C)
                        Printf("***Break\n");
                      else
                        Wait(1L<<signal);   // Wait for half-loaded buffer to finish.
 
                      AHI_ControlAudio(actrl,
                          AHIC_Play,FALSE,
                          TAG_DONE);
                    }
                    FreeSignal(signal);
                    signal=-1;
                  }
 
                }
                else
                  Printf("Cannot intialize sample buffers\n");
              }
              else
                Printf("Out of memory.\n");
 
              FreeVec(Sample0.ahisi_Address);
              FreeVec(Sample1.ahisi_Address);
              Sample0.ahisi_Address=NULL;
              Sample1.ahisi_Address=NULL;
              AHI_FreeAudio(actrl);
              actrl=NULL;
            }
            else
              Printf("Unable to allocate sound hardware\n");
          }
          else
          {
            if(IoErr() EQ NULL)
              Printf("Aborted.\n");
            else if(IoErr() EQ ERROR_NO_MORE_ENTRIES)
              Printf("No available audio modes.\n");
            else if(IoErr() EQ ERROR_NO_FREE_STORE)
              Printf("Out of memory.\n");
          }
 
          AHI_FreeAudioRequest(req);
          req=NULL;
        }
        else
          Printf("Out of memory.\n");
 
        CloseDevice((struct IORequest *)AHIio);
        AHIDevice=-1; // Good habit, IMHO.
      }
      DeleteIORequest((struct IORequest *)AHIio);
      AHIio=NULL;
    }
    DeleteMsgPort(AHImp);
    AHImp=NULL;
  }
}

Recording Example

/*
** This program uses the device interface to sample sound data.
** The output is written to THE DEFAULT OUTPUT, make sure you
** start it with "RecordTest > mysample.raw" !
*/
 
#include <devices/ahi.h>
#include <dos/dosasl.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/ahi.h>
#include <stdlib.h>
 
#define FREQUENCY 8000
#define TYPE       AHIST_M8S
#define BUFFERSIZE 20000
 
struct Library    *AHIBase;
struct MsgPort    *AHImp=NULL;
struct AHIRequest *AHIio=NULL;
BYTE               AHIDevice=-1;
 
BYTE buffer1[BUFFERSIZE];
BYTE buffer2[BUFFERSIZE];
 
void cleanup(LONG rc)
{
  if(!AHIDevice)
    CloseDevice((struct IORequest *)AHIio);
  DeleteIORequest((struct IORequest *)AHIio);
  DeleteMsgPort(AHImp);
  exit(rc);
}
 
void main(int argc, char *argv[])
{
  BYTE *p1=buffer1,*p2=buffer2,*tmp;
  ULONG signals;
 
  if(AHImp=CreateMsgPort()) {
    if(AHIio=(struct AHIRequest *)CreateIORequest(AHImp,sizeof(struct AHIRequest))) {
      AHIio->ahir_Version = 4;
      AHIDevice=OpenDevice(AHINAME,0,(struct IORequest *)AHIio,NULL);
    }
  }
 
  if(AHIDevice) {
    Printf("Unable to open %s/0 version 4\n",AHINAME);
    cleanup(RETURN_FAIL);
  }
 
// Initialize the first read
  AHIio->ahir_Std.io_Command=CMD_READ;
  AHIio->ahir_Std.io_Data=&buffer1;
  AHIio->ahir_Std.io_Length=BUFFERSIZE;
  AHIio->ahir_Std.io_Offset=0;
  AHIio->ahir_Frequency=FREQUENCY;
  AHIio->ahir_Type=TYPE;
  if(!DoIO((struct IORequest *) AHIio)) {
 
// The first buffer is now filled
    SetIoErr(NULL);
 
    for(;;) {
      ULONG length;
 
      length=AHIio->ahir_Std.io_Actual;
 
// Initialize the second read (note that io_Offset is not cleared!)
      AHIio->ahir_Std.io_Data=p2;
      AHIio->ahir_Std.io_Length=BUFFERSIZE;
      AHIio->ahir_Frequency=FREQUENCY;
      AHIio->ahir_Type=TYPE;
      SendIO((struct IORequest *) AHIio);
 
// While the second read is in progress, save the first buffer to stdout
      if(Write(Output(),p1,length) != length) {
        break;
      }
 
      signals=Wait(SIGBREAKF_CTRL_C | (1L << AHImp->mp_SigBit));
 
      if(signals & SIGBREAKF_CTRL_C) {
        SetIoErr(ERROR_BREAK);
        break;
      }
 
// Remove the reply 
      if(WaitIO((struct IORequest *) AHIio)) {
        SetIoErr(ERROR_READ_PROTECTED);
        break;
      }
 
// Swap buffer pointers and repeat
      tmp=p1;
      p1=p2;
      p2=tmp;
    }
 
// Abort any pending iorequests
    AbortIO((struct IORequest *) AHIio);
    WaitIO((struct IORequest *) AHIio);
  }
 
  if(IoErr())
  {
    PrintFault(IoErr(), argv[0] ); // Oh, common! It's not MY fault that this
                                   // routine prints to stdout instead of stderr!
    cleanup(RETURN_ERROR);
  }
 
  cleanup(RETURN_OK);
}