/* 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 <exec/types.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <proto/exec.h>
#include <custom/cleanup.h>
#include <dos/dos.h>
#include <stdio.h>
#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<<port->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();
	}

