Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "AHI Device"
Line 72: | Line 72: | ||
=== Writing to the AHI Device === |
=== 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. |
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 ==== |
||
+ | ==== Distortion ==== |
||
+ | ==== Multiple Sounds at the Same Time ==== |
||
+ | ==== Suggested Precedences ==== |
||
+ | {| class="wikitable" |
||
+ | !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 should be at this level; the rest should be set to sound effects levels. |
||
+ | |- |
||
+ | |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). |
||
+ | |} |
||
=== Closing the AHI Device === |
=== Closing the AHI Device === |
Revision as of 22:07, 19 March 2017
Contents
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
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
Distortion
Multiple Sounds at the Same Time
Suggested Precedences
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 should be at this level; the rest should be set to sound effects levels. |
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). |
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); }