Copyright (c) Hyperion Entertainment and contributors.
AHI Device
Contents
About AHI
AHI is a retargetable audio subsystem which provides standardized operating system support for wide range of audio hardware. AHI offers improved functionality not available through the AmigaOS audio device driver, such as seamless audio playback from a user selected audio device, standardized functionality for audio recording and efficient software mixing routines for combining multiple sound channels.
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
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
Reading from the AHI Device
Writing to the AHI Device
Closing the AHI Device
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); }