Copyright (c) Hyperion Entertainment and contributors.
AHI Device: Difference between revisions
Line 79: | Line 79: | ||
!Precedences!!Type of Sound |
!Precedences!!Type of Sound |
||
|- |
|- |
||
|127||Unstoppable. Sounds first allocated at lower precedencies, then set to this highest level. |
| 127||Unstoppable. Sounds first allocated at lower precedencies, then set to this highest level. |
||
|- |
|- |
||
|90-100||Emergencies. Alert, urgent situation that requires immediate action. |
| 90 - 100||Emergencies. Alert, urgent situation that requires immediate action. |
||
|- |
|- |
||
|80-90||Annunciators. Attention, bell (CTRL-G). |
| 80 - 90||Annunciators. Attention, bell (CTRL-G). |
||
|- |
|- |
||
|75||Speech. Synthesized or recorded speech. |
| 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 - 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. |
| -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. |
| -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. |
| -100 - -80||Background. Theme music and restartable background sounds. |
||
|- |
|- |
||
|128||Silence. Lowest level (freeing the channel completely is preferred). |
| -128||Silence. Lowest level (freeing the channel completely is preferred). |
||
|} |
|} |
||
Revision as of 21:09, 19 March 2017
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);
}