/* audio_soft.c */ /* $Author: Espie $ * $Date: 91/05/16 15:04:36 $ * $Revision: 1.22 $ * $Log: audio_soft.c,v $ * Revision 1.22 91/05/16 15:04:36 Espie * *** empty log message *** * * Revision 1.21 91/05/12 19:56:53 Espie * Added some logic so that play can be triggered by the availability * of a song now. * * Revision 1.20 91/05/12 16:00:59 Espie * Tells the interface when we own the audio.device and when not. * * Revision 1.19 91/05/07 12:14:13 Espie * *** empty log message *** * * Revision 1.18 91/05/06 15:14:12 Espie * Added a measure of real-life error checking. * * Revision 1.17 91/05/05 04:00:05 Espie * Suppressed recursive calls. * * Revision 1.16 91/05/02 23:29:22 Espie * Checked the automaton. Now reliable. * There definitely seems to be a bug in the audio.device... * * Revision 1.15 91/05/02 11:19:42 Espie * Checked out the new automaton. Still needs some error checking. * * Revision 1.14 91/05/02 01:29:49 Espie * The automaton is now implemented as an automaton, instead * of a program calling the events handling from time to time. * * Revision 1.13 91/04/30 00:35:41 Espie * Stable version III. * * Revision 1.12 91/04/29 23:55:36 Espie * Cleaned up the allocation key bunch. * This version exhibits a small problem with the OS. * When there are too many openers around, AbortIO() will * sometimes wait for something to happen... * * Revision 1.11 91/04/29 15:03:22 Espie * Corrected a small problem: safeabort() should be called BEFORE * closing the audio.device. * * Revision 1.10 91/04/29 13:02:53 Espie * Completely clean audio_soft. * (Except maybe for the request duplication). * * Revision 1.9 91/04/29 03:53:39 Espie * Encapsulating more and more, not yet ready. * * Revision 1.8 91/04/29 02:22:50 Espie * Suppressed critical sections, should be cleaner now, * and easier to debug. * * Revision 1.7 91/04/28 20:35:59 Espie * Small changes. * * Revision 1.6 91/04/27 16:45:23 Espie * Moved interrupt disabling to audio_hard. * * Revision 1.5 91/04/27 04:01:28 Espie * * NOW DISABLES THE AUDIO.DEVICE INTERRUPTS. * DON'T KNOW IF THAT IS NECESSARY, DON'T KNOW * IF THAT IS VERY CLEAN EITHER. * * Revision 1.4 91/04/26 16:31:51 Espie * Completely new audio_soft.c. * The RKM is not very clear. * If a channel is stolen, you don't have to free the channel, * a CMD_RESET is enough. * To get back your channel, don't change the allocation key, * just ask a CMD_ALLOCATE again. * So that there are now two behaviours: either we allocate/deallocate channels * (pauses), or we get a channel stolen, in which case, we just CMD_RESET * and CMD_ALLOCATE again with the same key/CMD_LOCK again. * The ``critical section'' parts aren't really handled yet (read: kludge). * * Revision 1.3 91/04/24 16:09:40 Espie * Corrected a big hanging bug (CTRL C while waiting for a request triggered * wrong logic). * * Revision 1.2 91/04/24 15:23:42 Espie * Arbitration between different users of the audio channel. * A bit crude yet. * Amazingly enough, it works. * Should provide a way to release the CIA timer too, might just * be useful... * There are lots of critical sections, not sure if everything is ok right now. * * Revision 1.1 91/04/23 21:31:03 Espie * Initial revision * * */ /* how to obtain access to the audio hardware, and release it gracefully */ #include #include #include #include #include #include #include #include "proto.h" #include "player.h" FORWARD LOCAL void free_channels(); FORWARD LOCAL void process_messages(); LOCAL struct IOAudio *req, *lock; LOCAL struct MsgPort *port; LOCAL ULONG portmask; /* state of the know requests */ LOCAL BOOL pending_request, pending_lock; LOCAL BYTE last_error; /* My audio state machine. */ LOCAL enum {BEGIN, ALLOCATING, AWAIT_STOLEN, STOLEN, FREEING } state; LOCAL BOOL should_run, song_avail, playing; LOCAL CLEAN audclean; /* setup_hard(). reset the audio hardware to a nice starting state. */ LOCAL void setup_hard(void) { audclean = AllocClean(NIL); ToClean0L(audclean, reset_audio); save_filter(); ToClean0L(audclean, restore_filter); reset_audio(); } /* clean up after one self */ LOCAL void safeabort() { /* critical section: DON'T try to abort a success allocation */ Forbid(); process_messages(); if (state == ALLOCATING) { if (pending_request) { /* AbortIO() always succeeds */ AbortIO(req); while(pending_request) { WaitPort(port); process_messages(); } } else state = AWAIT_STOLEN; } Permit(); if (state == AWAIT_STOLEN && !last_error) { free_channels(); } while(pending_request || pending_lock) { WaitPort(port); process_messages(); } } /* obtain_audio(): allocate all the structures we will need to * play with the audio device, and reset the state machine. */ ULONG obtain_audio(int priority) { BYTE fail; pending_request = FALSE; pending_lock = FALSE; state = BEGIN; port = CreatePort(NULL, 0); if (port) ToClean(DeletePort, port); else mayPanic("Couldn't allocate port"); portmask = 1L<mp_SigBit; /* should be replaced by a call to CreateExtIO(), except that it isn't * really easy (2.0 system/ 1.3 link library) */ req = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_CLEAR|MEMF_PUBLIC); if (req) ToClean2(FreeMem, req, sizeof(struct IOAudio)); else mayPanic("Couldn't allocate first request"); lock = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_CLEAR|MEMF_PUBLIC); if (lock) ToClean2(FreeMem, lock, sizeof(struct IOAudio)); else mayPanic("Couldn't allocate first request"); req->ioa_Request.io_Message.mn_ReplyPort = port; req->ioa_Request.io_Message.mn_Node.ln_Pri = priority; req->ioa_AllocKey = 0; /* Note that OpenDevice returns 0 on success */ fail = OpenDevice("audio.device", 0L, (struct IORequest *)req, 0L); if (fail) mayPanic("Couldn't open audio device"); else ToClean(CloseDevice, req); ToClean0(safeabort); state = BEGIN; /* song_avail = FALSE; */ return portmask; } LOCAL void void_pending(struct Message *msg) { if (msg == &(lock->ioa_Request.io_Message)) { pending_lock = FALSE; last_error = lock->ioa_Request.io_Error; } else if (msg == &(req->ioa_Request.io_Message)) { pending_request = FALSE; last_error = req->ioa_Request.io_Error; } } LOCAL void process_messages(void) { struct Message *msg; while(msg = GetMsg(port)) void_pending(msg); } /*** * * basic communication with the audio hardware. * You shouldn't use these directly. * ***/ LOCAL UBYTE whichchannel[] = {15}; /* allocate_channels(): sends a request to the audio.device for * the channels. */ LOCAL void allocate_channels(void) { /* All channels ! */ req->ioa_Request.io_Command = ADCMD_ALLOCATE; req->ioa_Data = whichchannel; req->ioa_Length = sizeof(whichchannel); BeginIO(req); pending_request = TRUE; state = ALLOCATING; } /* lock_channels(): access to the audio device has been granted to us, * we lock the channels */ LOCAL void lock_channels(void) { *lock = *req; lock->ioa_Request.io_Command = ADCMD_LOCK; BeginIO(lock); pending_lock = TRUE; } /* free_channels(): give back the channels. */ LOCAL void free_channels(void) { req->ioa_Request.io_Command = ADCMD_FREE; BeginIO(req); pending_request = TRUE; } LOCAL void own_audio(void) { setup_hard(); start_timer(); have_audio(TRUE); state = AWAIT_STOLEN; } LOCAL void release_audio(void) { stop_timer(); CleanUp(audclean); have_audio(FALSE); free_channels(); } /* how to make things happen */ LOCAL void trigger_player() { should_run = playing & song_avail; if (should_run) { if (state == BEGIN) { allocate_channels(); } } else { if (state == AWAIT_STOLEN) { release_audio(); state = FREEING; } } } /* the state machine itself, complete with error checking... */ void handle_audio() { /* this is a terminal recursive function, * we optimize by hand since the compiler doesn't do it for us... */ term_rec: process_messages(); switch(state) { case ALLOCATING: if (pending_request) break; if (last_error) /* this one we don't know how to process */ mayPanic("audio channel error"); lock_channels(); process_messages(); if (last_error) { /* could not lock the channel */ state = STOLEN; goto term_rec; } if (should_run) { own_audio(); } else trigger_player(); goto term_rec; break; case AWAIT_STOLEN: if (pending_lock) { if (!should_run) { trigger_player(); goto term_rec; } } else { if (last_error == ADIOERR_CHANNELSTOLEN) { release_audio(); state = STOLEN; } else mayPanic("Audio Lock aborted"); } break; case STOLEN: if (pending_request) break; if (last_error) mayPanic("Could not free channel properly"); state = BEGIN; if (should_run) { trigger_player(); goto term_rec; } break; case FREEING: if (pending_request || pending_lock) break; if (last_error) mayPanic("Another audio.device problem"); state = BEGIN; if (should_run) { trigger_player(); goto term_rec; } break; case BEGIN: break; } } void start_player() { playing = TRUE; trigger_player(); handle_audio(); } void stop_player() { playing = FALSE; trigger_player(); handle_audio(); } void song_available(BOOL really) { song_avail = really; trigger_player(); handle_audio(); }