/* audiotools.c */ #define DEBUG 1 #include "exec/types.h" #include "exec/memory.h" #include "devices/audio.h" #include "ram:audiotools.h" #include "ram:globals.c" extern APTR AllocMem(); extern struct Message *GetMsg(); main() { LONG i, channel, error; InitAudio(); for(i=0; i<4; i++) { channel = GetChannel(-1); if(channel == -1) finishup("cannot get a channel!"); /* At this point, must save globals from gotkey, gotunit */ key[i] = gotkey; /* save allocation key */ unit[i] = gotunit; /* save unit value */ error = StopChannel(channel); if(error) { printf("error in stopping channel = %ld\n",error); finishup("StopChannel did not work as expected"); } } /* (channel, note, waveform, vol, duration, priority,message) */ for(i=0; i<95; i++) { PlayNote(0, i, w1, 32, 250, 0, 0); /* all notes, 1/4 sec. */ } error = StartChannel(0); Delay(800); /* let most of them play... this waits 16 seconds */ for(i=1; i<4; i++) { error = StartChannel(i); if(error) printf("error starting channel = %ld\n",error); } PlayNote(0, 23, w1, 32, 2000, 0, 0); PlayNote(1, 27, w2, 32, 2300, 0, 0); PlayNote(2, 30, w3, 32, 2600, 0, 0); PlayNote(3, 35, w1, 32, 2900, 0, 0); FinishAudio(); return(0); } /* end of main() */ InitAudio() { int error,i; /* Declare all message blocks available */ for(i=0; iioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = controlPort; InitBlock(iob,channel); /* init it for CMD_WRITE, then change */ iob->ioa_Request.io_Command = command; iob->ioa_Request.io_Flags = IOF_QUICK; BeginIO(iob); WaitIO(iob); rtn = ((LONG)(iob->ioa_Request.io_Error)); return(rtn); } struct ExtIOB * GetIOB(ch) LONG ch; { WORD i,use_reply; struct ExtIOB *iob; /* in case we need to allocate one */ ReEmployIOB(); /* find already used ones and free them */ /* so that when we do a get... */ if(ch == -1) use_reply = 0; /* which reply port to use */ else use_reply = ch; for(i=0; iioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = replyPort[use_reply]; iob->ioa_Request.io_Message.mn_Node.ln_Name = dynamicname; iob->ioa_Request.io_Message.mn_Length = dynamix[use_reply]; dynamix[use_reply] += 1; /* add one to number allocated * for a specific channel */ #ifdef DEBUG printf("Allocated a new dynamic iob\n"); #endif DEBUG return(iob); } return(0); } /* ReEmployIOB - look at all of the reply ports and if any IOBs * hanging around with nothing to do, free them. */ ReEmployIOB() { LONG i; struct MsgPort *mp; struct ExtIOB *iob; for(i=0; i<4; i++) /* remove all iob's from all ports */ { mp = replyPort[i]; while( (iob = (struct ExtIOB *)GetMsg(mp)) != 0) { #ifdef DEBUG printf("type of iob freed is: %ls\n", iob->ioa_Request.io_Message.mn_Node.ln_Name); printf("its identifier value is: %ld\n", iob->ioa_Request.io_Message.mn_Length); #endif DEBUG FreeIOB(iob, i); } } return(0); } /* Free a global or an allocated IOB */ int FreeIOB(iob, ch) struct ExtIOB *iob; LONG ch; /* which channel was it attached to? */ { WORD i; if(iob->ioa_Request.io_Message.mn_Node.ln_Name == dynamicname) { FreeMem(iob, sizeof(struct ExtIOB)); if(dynamix[ch]) dynamix[ch] -= 1; /* subtract one if nonzero */ return(0L); } else if(iob->ioa_Request.io_Message.mn_Node.ln_Name == globalname) { i = iob->ioa_Request.io_Message.mn_Length; #ifdef DEBUG printf("Freeing global buffer numbered %ld\n",i); #endif DEBUG if(i < AUDBUFFERS) { inuse[i] = NO; /* frees this one for reuse */ } return(0L); } /* if get here, the names don't match... something is wrong.*/ else { printf("FreeIOB: names don't match...unknown error\n"); return(-1); /* unknown source of IOB fed to routine. */ } return(0); } /* Initialize an audio I/O block for default CMD_WRITE operation. */ int InitBlock(iob, channel) struct ExtIOB *iob; WORD channel; { /* Device and ReplyPort fields have been initialized by GetIOB */ iob->ioa_Request.io_Unit = unit[channel]; /* Allocation key */ iob->ioa_AllocKey = key[channel]; /* Where is the waveform? Just be sure is in MEMF_CHIP!!! */ /* USER initializes datalength[ch] before calling this; */ /* for sampled sound command write operation. */ iob->ioa_Data = chipaudio[channel]; iob->ioa_Length = datalength[channel]; /* Another routine, must initialize: period ioa_Period volume ioa_Volume cycles ioa_Cycles message ioa_WriteMessage */ /* Default command type is CMD_WRITE */ iob->ioa_Request.io_Command = CMD_WRITE; /* If IOF_QUICK is zeroed, this would affect the * period and volume. If a CMD_WRITE, it queues if * another note is already playing. We queue CMD_WRITES. */ iob->ioa_Request.io_Flags = ADIOF_PERVOL; return(0); } /* To request "any" stereo pair, use pair = -1; * To request a specific stereo pair, use pair = {0, 1, 2 or 3} * corresponding to channels 0 and 1, 0 and 2, 1 and 2 or 1 and 3 * respectively. */ int GetStereoPair(pair) LONG pair; { int error, value; struct ExtIOB *iob, controlIOB; iob = &controlIOB; iob->ioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = controlPort; InitBlock(iob,0); /* init it for CMD_WRITE, then change */ /* set precedence of the request for a channel */ iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20; /* Type of command is ALLOCATE */ iob->ioa_Request.io_Command = ADCMD_ALLOCATE; if(pair == -1) { /* Point to the allocation map */ iob->ioa_Data = (UBYTE *)stereostuff; /* It contains 4 entries */ iob->ioa_Length = 4; } else if(pair >=0 && pair <= 3) { iob->ioa_Data = (UBYTE *)(&stereostuff[pair]); iob->ioa_Length = 1; } else /* chose a bad channel pair; cannot allocate it */ { return(-1); } /* Don't wait for allocation, channels * should be available! If we don't set * ADIOF_NOWAIT, the task will idle waiting * for a chance to allocate the channel, * looking again each time another task * allocates or frees a channel. */ iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK; BeginIO(iob); error = WaitIO(iob); /* returns nonzero if error */ if(!(iob->ioa_Request.io_Flags & IOF_QUICK)) { /* if flag not set, then the message * was appended to the reply port * (was not quick I/O after all) */ GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort); } if(error) { return(-1); } /* Save the values... freeing the IOB on exit */ gotunit = (iob->ioa_Request.io_Unit); gotkey = (iob->ioa_AllocKey); switch((LONG)(iob->ioa_Request.io_Unit)) { case 3: value = 0; break; case 5: value = 1; break; case 10: value = 2; break; case 12: value = 3; break; default: value = -1; break; } return(value); } /* To request "any" channel, use ch = -1; * To request a specific channel, use ch = {0, 1, 2 or 3}; */ int GetChannel(ch) LONG ch; { int error, value; struct ExtIOB *iob, controlIOB; iob = &controlIOB; iob->ioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = controlPort; InitBlock(iob,0); /* init it for CMD_WRITE, then change */ iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20; iob->ioa_Request.io_Command = ADCMD_ALLOCATE; if(ch == -1) { iob->ioa_Data = (UBYTE *)anychan; iob->ioa_Length = 4; } else if(ch >=0 && ch <= 3) { iob->ioa_Data = (UBYTE *)(&anychan[ch]); iob->ioa_Length = 1; } else /* chose a bad channel number; cannot allocate it */ { return(-1); } iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK; BeginIO(iob); error = WaitIO(iob); /* returns nonzero if error */ if(!(iob->ioa_Request.io_Flags & IOF_QUICK)) { GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort); } if(error) { return(-1); } gotunit = (iob->ioa_Request.io_Unit); gotkey = (iob->ioa_AllocKey); switch((LONG)(iob->ioa_Request.io_Unit)) { case 1: value = 0; break; case 2: value = 1; break; case 4: value = 2; break; case 8: value = 3; break; default: value = -1; break; } return(value); } int FreeChannel(ch) LONG ch; { int error; struct ExtIOB *iob, controlIOB; iob = &controlIOB; iob->ioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = controlPort; InitBlock(iob,ch); /* init it for CMD_WRITE, then change */ /* (pick up unit and key value for channel) */ iob->ioa_Request.io_Command = ADCMD_FREE; iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK; BeginIO(iob); error = WaitIO(iob); /* returns nonzero if error */ if(!(iob->ioa_Request.io_Flags & IOF_QUICK)) { GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort); } if(error) { return(-1); } return(0); } /* NOTE: FreeChannel should work as FreeStereoPair(pair) too! */ /* THE FOLLOWING ROUTINES ARE PARAPHRASED FROM A USENET and BIX * POSTING MADE IN 1985 BY STEVEN A. BENNETT. */ /* I have modified his routines to queue the audio commands in * place of starting forever-duration and canceling each note. * Many of his original comments have been incorporated into * the article. */ /* PlayNote(...) */ /* Starts a sound on the channel with specified period and volume. */ /* This nice little routine takes a note and plays it on the given * voice. The note is basically an integer from * 0 to 11 (c to b) plus 12 per octave above the first and lowest. * * The waveform to use is determined by adding an index (woffsets[]) * dependant on the octave. * * The length of the waveform (in wlen[]) is likewise dependant on * the octave. Note that octaves start with zero, not one. */ int PlayNote(channel, note, wf, vol, duration, priority, message) char *wf; /* waveform to use */ LONG vol, channel, duration, note; /* specific note number */ LONG priority; struct Message *message; { LONG per, len, oct; /* period, length of waveform, which octave */ char *wavepointer; /* where to find start of waveform */ struct ExtIOB *iob; int frequency; iob = GetIOB(channel); if(iob != 0) { InitBlock(iob, channel); /* set up for CMD_WRITE */ oct = note / 12; wavepointer = wf + woffsets[oct]; len = wlen[oct]; per = perval[note % 12]; /* Set the parameters */ iob->ioa_Data = (UBYTE *)wavepointer; iob->ioa_Length = len; iob->ioa_Period = per; iob->ioa_Volume = vol; /* PlayNote (continued) */ /* Look at the frequency that it is to play by backwards calc. */ frequency = 3579545 / (len * per); /* Calculate cycles from duration in 1000ths of a second */ /* Multiply all-in-one to maintain max precision possible */ /* (all integer arithmetic.) */ iob->ioa_Cycles = ((LONG)(frequency * duration)/1000); BeginIO(iob); return(0); /* all went ok */ } else { return(-1); /* couldnt get IOB */ } return(0); } /* SetPV(channel, per, vol) * int channel, per, vol; */ int SetPV(channel, per, vol) int channel, per, vol; { int error; struct ExtIOB *iob, controlIOB; iob = &controlIOB; iob->ioa_Request.io_Device = device; iob->ioa_Request.io_Message.mn_ReplyPort = controlPort; InitBlock(iob, channel); /* set up for CMD_WRITE */ iob->ioa_Period = per; iob->ioa_Volume = vol; iob->ioa_Request.io_Command = ADCMD_PERVOL; iob->ioa_Request.io_Flags = IOF_QUICK | ADIOF_PERVOL; BeginIO(iob); /* This one will be synchronous; affects whatever * is playing on this channel at this time. */ error = WaitIO(iob); /* OK to wait, since it will return */ return(error); /* copy of io_Error field; should be 0 */ } /* SetWaves(w1, w2, w3): create first sawtooth, triangle and square wave */ SetWaves(w1, w2, w3) UBYTE *w1, *w2, *w3; { int i, increment, value, sqvalue; value = 0; increment = 2; sqvalue = 127; for (i = 0; i < BIG_WAVE; ++i) { w1[i] = i; /* do the sawtooth */ if(i > 62 && i < 180) increment = -2; else if(i >= 180) increment = 2; w2[i] = value; value += increment; /* triangle wave */ if(i > 126) sqvalue = -127; w3[i] = sqvalue; } return(0); } /* ExpandWave(wfp) - replicate waves in decreasing sample sizes * BYTE *wfp; */ ExpandWave(wfp) BYTE *wfp; { int i, j, rate; BYTE *tptr; rate = 1; tptr = wfp + BIG_WAVE; for (i = 0; i < NBR_WAVES - 1; ++i) { rate *= 2; for (j = 0; j < BIG_WAVE; j += rate) *tptr++ = wfp[j]; } return(0); } /* MakeWaves() * * Just makes a sawtooth, triangle and square wave in chip mem * and expands them. */ int MakeWaves() { /* allocate the memory for the waveforms. */ w1 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP); w2 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP); w3 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP); if (w1 == NULL || w2 == NULL || w3 == NULL) return(-1); /* ran out of memory! */ /* get and expand the waveforms */ SetWaves(w1, w2, w3); ExpandWave(w1); chipaudio[0]=w1; ExpandWave(w2); chipaudio[1]=w2; ExpandWave(w3); chipaudio[2]=w3; return(0); }