home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
348.lha
/
chatterbox_v1.0
/
sources
/
player.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-02-14
|
12KB
|
450 lines
/* player - sound playing portion of karl's smus player */
/* hacked to be an effects player */
/* note - "channels", throughout the code below, are a structure I have
* invented, a logical channel sort of thing that is useful for what
* I'm trying to do. There is not a 1-1 correspondence between these
* channels and the hardware channels, which hardware channel is used
* for a given channel is determined by audio.device when we make a
* request to allocate a channel.
*/
#include <exec/types.h>
#include <functions.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <stdio.h>
#include "assert.h"
#include "8svx.h"
#include "sample.h"
#include "channels.h"
#define YES 1
#define NO 0
short audio_up = NO, audio_down = NO;
extern int debug;
#ifdef DEBUG
#define WHERE fprintf(stderr,"%s %s %d\n",__FILE__,__FUNC__,__LINE__); fflush(stderr);
#define WHAT(x) fprintf(stderr,"%s %s %d %s\n",__FILE__,__FUNC__,__LINE__,x->name);
#else
#define WHERE
#define WHAT(x)
#endif
#define CLOCKFREQ 3579545
/* define the channel array - this is the top level of data for an audio
channel */
Channel Channels[MAX_CHANNELS];
struct Device *AudioDevice;
struct MsgPort *sfx_port;
struct SMUS_IOAudio *openIOB;
short MasterVolume = 127;
/* allocation maps for requesting channel assignments from audio.device:
* I use LeftMap and RightMap to insist on a stereo side - if it can't
* allocate (at play time) that side it will blow off the note.
* I use TryLeftMap and TryRightMap to indicate a preference for a
* stereo side, but if I can't have a channel on that side, it'll
* give me one from the other side if one is available there
*/
static UBYTE LeftMap[] = {LEFT0, LEFT1};
static UBYTE RightMap[] = {RIGHT0, RIGHT1};
static UBYTE TryLeftMap[] = {LEFT1, LEFT0, RIGHT1, RIGHT0};
static UBYTE TryRightMap[] = {RIGHT1, RIGHT0, LEFT1, LEFT0};
/* SMUS_IOBallocate - allocates a SMUS player IOB
* It shouldn't panic when the allocate fails, and will be eventually
* made to back out properly.
* Not much to the routine at all...
*/
struct SMUS_IOAudio *SMUS_IOBallocate()
{
struct SMUS_IOAudio *IOB;
extern UBYTE *MyAllocMem();
if ((IOB = (struct SMUS_IOAudio *)MyAllocMem(sizeof(struct SMUS_IOAudio), (long)MEMF_PUBLIC | MEMF_CLEAR)) == (struct SMUS_IOAudio *) NULL)
panic("SMUS_IOBallocate failed");
return(IOB);
}
/* free an IOB created by SMUS_IOBallocate */
SMUS_IOBfree(IOBp)
struct SMUS_IOAudio *IOBp;
{
if (IOBp != NULL)
FreeMem(IOBp, sizeof(struct SMUS_IOAudio));
}
/* Init the IOB for the oneshot portion of the sound */
SMUS_InitShotIOB(channel)
int channel;
{
struct SMUS_IOAudio *IOB = Channels[channel].IOBs[ONESHOT_IOB];
IOB->smusIOB.ioa_Request.io_Command = CMD_WRITE;
IOB->smusIOB.ioa_Request.io_Flags = ADIOF_PERVOL;
/* NEEDS WORK - needs to dynamically set volume based on volume
* commands we receive from the track, and the overall volume
* from the song structure */
IOB->smusIOB.ioa_Volume = 127;
/* cycles to repeat is 1, this is a oneshot sound */
IOB->smusIOB.ioa_Cycles = 1;
}
/* Init an IOB that will stop the playing sound */
SMUS_InitStopIOB(channel)
int channel;
{
struct SMUS_IOAudio *IOB = Channels[channel].IOBs[STOP_IOB];
/* I switched from ADCMD_FINISH to CMD_FLUSH because I
* can have up to three things queued on a channel
* and I want to kill them all - ADCMD_FINISH would only
* kill the one in progress
* IOB->smusIOB.ioa_Request.io_Command = ADCMD_FINISH;
*/
IOB->smusIOB.ioa_Request.io_Command = CMD_FLUSH;
IOB->smusIOB.ioa_Request.io_Flags = IOF_QUICK;
}
/* Init an IOB that will allocate a channel */
SMUS_InitAllocIOB(channel)
int channel;
{
struct SMUS_IOAudio *allocIOB = Channels[channel].IOBs[ALLOC_IOB];
static BYTE attack_precedences[] = {50, 40, 30, 20};
allocIOB->smusIOB.ioa_Request.io_Command = ADCMD_ALLOCATE;
allocIOB->smusIOB.ioa_Request.io_Message.mn_Node.ln_Pri = attack_precedences[channel];
/* The IFF SMUS spec says priority is to be highest for track 1
* and descending with progressively higher tracks.
*
* The audio.device doc in the ROM Kernel Manual says that
* higher priorities should be given for the attack portions
* of each note and that priorities for notes in a music
* program range from -50 (lowest) to 50 (highest)
*
* Consequently, since I'm orienting it toward four tracks on the
* Amiga, and this code is totally Amiga dependent anyway, I
* am assigning attack priorities of 50 for the first track, and
* each successive track minus 10, with the sustain portion of
* the note having its precedence lowered effectively by 50
*
*/
/* for now, channel 1 and 3 prefer the left channel, while 2 and 4
* prefer the right
*/
allocIOB->smusIOB.ioa_Data = (channel) ? TryLeftMap : TryRightMap;
allocIOB->smusIOB.ioa_Length = 4;
allocIOB->smusIOB.ioa_Request.io_Flags = (ADIOF_NOWAIT | IOF_QUICK);
}
/* Init an IOB that will deallocate the channel */
SMUS_InitDeallocIOB(channel)
int channel;
{
struct SMUS_IOAudio *IOB = Channels[channel].IOBs[FREE_IOB];
IOB->smusIOB.ioa_Request.io_Command = ADCMD_FREE;
IOB->smusIOB.ioa_Request.io_Flags = IOF_QUICK;
}
/* init a channel
*
* this guy takes care of allocating and initting all the different IOBs
*/
InitChannel(channelindex)
int channelindex;
{
int i;
struct SMUS_IOAudio *IOB;
Channel *channelp = &Channels[channelindex];
/* for all the IOBs,
* allocate the IOB and set the channel's IOB pointer to it
* set the channel pointer field that I tack onto the end
* of IOBs to point back to the channel (so that we can
* tell to what channel an IOB we receive on a message
* port belongs, if we need to)
*/
for (i = 0; i < MAX_CHANNEL_IOBS; i++)
{
IOB = channelp->IOBs[i] = SMUS_IOBallocate();
IOB->channelp = channelp; /* set pointer to IOB's Channel parent */
/* set replyport and device */
/* see someday if having the reply port is really neccessary */
IOB->smusIOB.ioa_Request.io_Message.mn_ReplyPort = sfx_port;
/* set the device in the request to the audio device */
IOB->smusIOB.ioa_Request.io_Device = AudioDevice;
/* let audio.device assign the alloc keys first time through */
IOB->smusIOB.ioa_AllocKey = 0;
}
/* initialize the different kinds of IOBs we use */
SMUS_InitShotIOB(channelindex);
SMUS_InitStopIOB(channelindex);
SMUS_InitAllocIOB(channelindex);
SMUS_InitDeallocIOB(channelindex);
channelp->samptr = NULL;
}
InitAudio()
{
int EndAudio();
int i;
openIOB = NULL;
AudioDevice = NULL;
add_cleanup(EndAudio);
openIOB = SMUS_IOBallocate();
if (OpenDevice(AUDIONAME, 0, &openIOB->smusIOB, 0) != 0)
panic("can't open audio device");
AudioDevice = openIOB->smusIOB.ioa_Request.io_Device;
if ((sfx_port = CreatePort((char *)NULL, 0)) == (struct MsgPort *)NULL)
panic("can't create sfx_port");
for (i = 0; i < MAX_CHANNELS; i++)
InitChannel(i);
audio_up = YES;
}
EndAudio()
{
int i, j;
Channel *channelp;
#ifdef DEBUG
fprintf(stderr,"endaudio cleanup routine executing\n");
#endif
if(audio_down)
return;
audio_down = YES;
if (audio_up)
{
for (i = 0; i < MAX_CHANNELS; i++)
StopChannelNote(&Channels[i]);
}
audio_up = NO;
if (sfx_port)
DeletePort(sfx_port,sizeof(struct MsgPort));
for (i = 0; i < MAX_CHANNELS; i++)
{
channelp = &Channels[i];
for (j = 0; j < MAX_CHANNEL_IOBS; j++)
{
SMUS_IOBfree(channelp->IOBs[j]);
channelp->IOBs[j] = NULL;
}
}
if(openIOB)
{
if(AudioDevice)
{
CloseDevice(openIOB);
AudioDevice = NULL;
}
SMUS_IOBfree(openIOB);
openIOB = NULL;
}
#ifdef DEBUG
fprintf(stderr,"endaudio cleanup routine complete\n");
#endif
}
/* the guts of note playing, right here */
PlayChannelSample(samptr,velocity,stereo_side)
Sample *samptr;
int velocity;
int stereo_side;
{
int i;
struct SMUS_IOAudio *iobptr;
Channel *channelp;
struct OctaveStruct *octave_ptr;
int octave, noctaves;
short shift_up_n_octaves = 0;
short shift_down_n_octaves = 0;
short period;
short volume;
int channel = -1;
struct SMUS_IOAudio *allocIOB;
struct SMUS_IOAudio *shotIOB;
/* make relatively certain samptr points to a good sample */
ASSERT_SAMPLE_MAGIC(samptr);
/* gimme one of my logical channels */
for (channel = 0; channel < 4; channel++)
{
channelp = &Channels[channel];
if (!(channelp->flags & (LOOP_PLAYING | SHOT_PLAYING)))
goto got_channel;
}
#ifdef DEBUG
printf("failed to get a channel, they're all in use\n");
#endif
return;
got_channel:
/* cons up some convenient pointers */
/* these assignments could be moved to where they're needed to prevent
* their being assigned but unused because of alloc failure,
* no need for oneshot, etc. */
allocIOB = channelp->IOBs[ALLOC_IOB];
shotIOB = channelp->IOBs[ONESHOT_IOB];
/* zero the alloc key so we'll get a fresh one - it's the only way
* to be sure we won't conflict with someone else */
/* allocIOB->smusIOB.ioa_AllocKey = 0; */
/* I think we're OK if we alloc it once */
allocIOB->smusIOB.ioa_Data = (stereo_side) ? TryLeftMap : TryRightMap;
BeginIO(&allocIOB->smusIOB);
/* the only possible error return is ADIOERR_NOALLOCATION */
if (allocIOB->smusIOB.ioa_Request.io_Error)
{
fprintf(stderr,"alloc failed on channel %d\n",channel);
return;
}
#ifdef DEBUG
fprintf(stderr,"channel %d allockey %d\n",channel,allocIOB->smusIOB.ioa_AllocKey);
#endif
channelp->samptr = samptr;
/* copy the Unit and AllocKey into the other IOBs
* This is not the coolest way to go.
* It doesn't need to copy it to all of them.
* Perhaps it should only copy it into the channel and
* the other routines would get it out of the channel and
* shove it into the IOB - I don't know, maybe later
* also, AllocKey need only be propogated once
*/
for (i = 0; i < MAX_CHANNEL_IOBS; i++)
{
iobptr = Channels[channel].IOBs[i];
iobptr->smusIOB.ioa_Request.io_Unit = allocIOB->smusIOB.ioa_Request.io_Unit;
iobptr->smusIOB.ioa_AllocKey = allocIOB->smusIOB.ioa_AllocKey;
}
assert(samptr->sampleheader.samplesPerSec != 0);
shotIOB->smusIOB.ioa_Period = (CLOCKFREQ / samptr->sampleheader.samplesPerSec);
if (make_sample_chip(samptr))
{
shotIOB->smusIOB.ioa_Data = (UBYTE *)samptr->sampledata;
shotIOB->smusIOB.ioa_Length = (ULONG)samptr->samplebytes;
shotIOB->smusIOB.ioa_Volume = 64;
shotIOB->smusIOB.ioa_Cycles = 1;
BeginIO(&shotIOB->smusIOB);
/* possible errors are IOERR_ABORTED and ADIOERR_NOALLOCATION */
if (shotIOB->smusIOB.ioa_Request.io_Error)
return;
channelp->flags |= SHOT_PLAYING;
#ifdef DEBUG
fprintf(stderr,"queued shot on channel %d\n",channel);
#endif
}
else
{
fprintf(stderr,"chatterbox: chip RAM allocation failed\n");
}
}
/* stop playing the channel if it's playing (we may not have been able to
* allocate a channel you know) and deallocate it
*/
/* memo to self: you should always stop all notes that stop in a
* certain time interval prior to starting any new ones
*/
StopChannelNote(channelp)
Channel *channelp;
{
struct SMUS_IOAudio *stopIOB = channelp->IOBs[STOP_IOB];
struct SMUS_IOAudio *freeIOB= channelp->IOBs[FREE_IOB];
if (channelp->flags & (LOOP_PLAYING | SHOT_PLAYING))
{
BeginIO(&stopIOB->smusIOB);
/* only error return is ADIOERR_NOALLOCATION */
if (channelp->flags & SHOT_PLAYING)
{
#ifdef DEBUG
fprintf(stderr,"SHOT playing\n");
#endif
WaitIO(&channelp->IOBs[ONESHOT_IOB]->smusIOB);
}
if (channelp->flags & LOOP_PLAYING)
{
#ifdef DEBUG
fprintf(stderr,"LOOP wait\n");
#endif
WaitIO(&channelp->IOBs[LOOP_IOB]->smusIOB);
/* error returns can be IOERR_ABORTED and ADIOERR_NOALLOCATION */
}
/* there's no point in waiting on precedence, it's synchronous */
channelp->flags &= ~(SHOT_PLAYING | LOOP_PLAYING);
#ifdef DEBUG
fprintf(stderr,"free channel %d, alloc key %d\n",channel,freeIOB->smusIOB.ioa_AllocKey);
#endif
BeginIO(&freeIOB->smusIOB);
/* only possible error return is ADIOERR_NOALLOCATION */
#ifndef NOAUDIT
if (freeIOB->smusIOB.ioa_Request.io_Error)
fprintf(stderr,"dealloc i/o error\n");
#endif
free_sample_chip(channelp->samptr);
}
}
/* I have forgone magic numbers on channel structures because of the static
* nature of the channels and their small number (4)
*/
struct SMUS_IOAudio *SMUS_GetMsg()
{
return((struct SMUS_IOAudio *)GetMsg(sfx_port));
}
/* end of player.c */