/* amiga/server/sound.c */

/* internal operations for the sound server itself */

/* $Id: sound.c,v 1.12 1994/01/08 20:26:45 Espie Rel Espie $
 * $Log: sound.c,v $
 * Revision 1.12  1994/01/08  20:26:45  Espie
 * Added pause gadget.
 *
 * Revision 1.11  1994/01/07  15:12:26  Espie
 * *** empty log message ***
 *
 * Revision 1.10  1994/01/07  01:01:49  Espie
 * Id.
 *
 * Revision 1.9  1994/01/05  16:11:43  Espie
 * Made TYPE_xxxSYNC explicit.
 *
 * Revision 1.8  1994/01/05  14:56:02  Espie
 * *** empty log message ***
 *
 * Revision 1.7  1994/01/05  04:35:23  Espie
 * Found spurious bug: I had to track through the various
 * possibilities of CMD_WRITE to finally realize I was losing my
 * messages... So when the queue was empty, I could not allocate
 * any channel...
 * Needed external program capability back in order though.
 * Problem was coming from data faronly, which is very incompatible
 * with printf.
 *
 * Note that now, we only care about reallocating channels when we're
 * doing a CMD_WRITE. doing it for ADCMD_PERVOL or CMD_FLUSH is pointless
 * since our old data have been completely forgotten in between...
 *
 * Some tests and masking are probably unnecessary. On the other hand, the
 * error code MUST be checked correctly. Maybe I'll try and make the
 * CMD_WRITE part clearer.
 *
 * Revision 1.6  1994/01/05  02:03:14  Espie
 * Mostly correct check for losing audio channels.
 *
 * Revision 1.5  1994/01/04  22:05:35  Espie
 * Cosmetic changes, mostly.
 *
 * Revision 1.4  1994/01/04  20:46:23  Espie
 * Plainly working version with all checks...
 *
 * Revision 1.3  1994/01/04  20:33:20  Espie
 * Mostly working version
 *
 * Revision 1.2  1994/01/04  19:13:21  Espie
 * Corrected most of the audio allocation/free muck-up.
 *
 * Revision 1.1  1994/01/04  15:45:37  Espie
 * Initial revision
 *
 */

#include <hardware/cia.h>
#include <hardware/intbits.h>
#include <hardware/dmabits.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <proto/exec.h>
#include <stddef.h>

#include <stdio.h>


#include "defs.h"
#include "amiga/amiga.h"
#include "amiga/server/server.h"

ID("$Id$")
extern volatile struct CIA __far ciaa;

UBYTE saved_filter, current_filter;

/* we allow for lots more audio requests than we need */
#define QUEUE_LENGTH 50


LOCAL struct ext_audio
   {
   struct MinNode node;
   struct IOAudio request;
   }
/* for sending immediate commands */
 *immediate = 0;
/* the specific request that gets used for closing/opening */
struct IOAudio *req = 0;

/* the audio queue */
struct MinList queue;

/* keep track of progress */
struct MinList pending;

LOCAL struct MsgPort *aport = 0;
/* in order to clean up afterwards: */
LOCAL boolean audio_opened = FALSE;

void send_immediate(ULONG command, int mask);
LOCAL void get_requests(void);

/* mask for allocation */
LOCAL UBYTE whichchannel[1];
/* mask of owned channels */
LOCAL UBYTE owned;

/* allocate_channels(mask): sends a request to the audio.device for
 * the channels described by mask
 */
LOCAL void allocate_channels(UBYTE mask)
   { 
   whichchannel[0] = mask & ~owned;
   req->ioa_Request.io_Command = ADCMD_ALLOCATE;
   req->ioa_Data = whichchannel;
   req->ioa_Length = sizeof(whichchannel);
   req->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
   BeginIO(req);
   WaitIO(req);
   if (!req->ioa_Request.io_Error)     /* update owned channels mask */
      {
      owned |= (int)req->ioa_Request.io_Unit;
#ifdef EXTERNAL
      printf("Allocated: %d (%d) = %d\n", (int)mask, (int)req->ioa_Request.io_Unit, (int)owned);
      fflush(stdout);
#endif
      }
#ifdef EXTERNAL
   else
      printf("Failed: %d (%d) = %d\n", req->ioa_Request.io_Error, mask, owned);
#endif
   ciaa.ciapra = (ciaa.ciapra & ~ CIAF_LED) | current_filter;
   }

/* free_channels(): give back all channels.
 */ 
LOCAL void free_channels(void)
   {
   if (req->ioa_AllocKey)
      {
      req->ioa_Request.io_Command = ADCMD_FREE;
      req->ioa_Request.io_Unit = owned;
      BeginIO(req);
      WaitIO(req);
#ifdef EXTERNAL
      printf("Freed !\n");
      fflush(stdout);
#endif
      owned = 0;
      }
   }

LOCAL struct ext_audio *create_request(void)
   {
   struct ext_audio *new;
         /* CHECK: no test for failed allocation */
   new = AllocMem(sizeof(struct ext_audio), MEMF_CLEAR|MEMF_PUBLIC);
   if (req && new)
      new->request = *req;
   return new;
   }

/* actual = create_queue(n): creates a queue of n audio requests,
 *    returns the actual number
 */   
LOCAL int create_queue(int n)
   {
   int i;
   struct ext_audio *new;

   for (i = 0; i < n; i++)
      {
      new = create_request();
      if (!new)
         return i;
      AddTail(&queue, new);
      }  
   return i;
   }

/* get back available requests */
LOCAL void get_requests(void)
   {
   struct IOAudio *back;

   while(back = GetMsg(aport))
      {
      struct ext_audio *full;
      full = (struct ext_audio *)
         (((UBYTE *)back) - offsetof(struct ext_audio, request));
      Remove(full);           /* no longer pending */
      AddTail(&queue, full);  /* ... and available */
      }
   }

/* send immediate command.
 * WARNING: only use send_immediate with commands which
 * are GUARANTEED to be synchronous if IOF_QUICK is set
 */
void send_immediate(ULONG command, int mask)
   {
   immediate->request.ioa_Request.io_Command = command;
   immediate->request.ioa_Request.io_Flags = IOF_QUICK;
   immediate->request.ioa_Request.io_Unit = (void *)(mask & owned);
   immediate->request.ioa_AllocKey = req->ioa_AllocKey;
   BeginIO(&immediate->request);
   return;     
   }

void reset_audio(void)
   {
   send_immediate(CMD_FLUSH, 15);
   get_requests();
   free_channels();
   }

/* obtain_audio(): allocate all the structures we will need to
 * play with the audio device
 */ 
LOCAL void obtain_audio(void)
   {
   BYTE fail;
   
   aport = CreateMsgPort();
   if (!aport)
      return;
   req = CreateIORequest(aport, sizeof(struct IOAudio));
   if (!req)
      return;
   req->ioa_AllocKey = 0;
   req->ioa_Request.io_Message.mn_Node.ln_Pri = 0;
      /* Note that OpenDevice returns 0 on success
       */
   fail = OpenDevice("audio.device", 0L, (struct IORequest *)req, 0L);
   if (!fail)
      audio_opened = TRUE;
   }

struct MsgPort *start_audio(void)
   {
   NewList(&queue);     /* initialize these now for correct cleanup */
   NewList(&pending);

   owned = 0;           /* we own no channel right now */
   obtain_audio();
   if (!audio_opened || create_queue(QUEUE_LENGTH) < QUEUE_LENGTH)
      return 0;
   immediate = RemHead(&queue);
   saved_filter = ciaa.ciapra & CIAF_LED;
   current_filter = CIAF_LED;
   return aport;  
   }  

void end_audio()
   {
   struct ext_audio *sweep, *next;

   if (immediate)       /* get immediate back in queue for freeing it */
      AddTail(&queue, immediate);
   if (req)
      free_channels();
   get_requests();
      /* REMOVE ALL REQUESTS FROM THE PENDING LIST */
   SCANLIST(sweep, next, &pending, struct ext_audio *)
      {
      AbortIO(&sweep->request);
      WaitIO(&sweep->request);
      }
      /* Now we can close safely */
   if (audio_opened)
      CloseDevice((struct IORequest *)req);
   if (req)
      DeleteIORequest(req);
   if (aport)
      DeletePort(aport);

   SCANLIST(sweep, next, &queue, struct ext_audio *)
      FreeMem(sweep, sizeof(struct ext_audio));
   ciaa.ciapra = (ciaa.ciapra & ~CIAF_LED) | saved_filter;
   }


/* Perform musical events pertaining to the current time */
void do_events(struct List *e)
   {
   struct ext_message *msg, *msg2;
   
   while (msg = RemHead(e))
      {
      switch (msg->type)
         {
      case TYPE_WAIT:         /* next time to wait for: finished */
         AddHead(e, msg);
         return;
      case TYPE_FLUSH_CHANNEL:
         send_immediate(CMD_FLUSH, msg->data.info.channel_mask);
         break;
      case TYPE_SETUP:        /* setup is a two message command */
         msg2 = RemHead(e);
         
         if (msg2)
            {
            struct ext_audio *new;
            
            new = RemHead(&queue);
            if (new)
               {
               new->request.ioa_Request.io_Command = CMD_WRITE;
               if (msg2->data.info.pitch)
                  {
                  new->request.ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
                  new->request.ioa_Period = msg2->data.info.pitch;
                  new->request.ioa_Volume = msg2->data.info.volume;
                  }
               else
                  new->request.ioa_Request.io_Flags = IOF_QUICK;
               if ((owned & msg2->data.info.channel_mask) == 0)
                  allocate_channels(msg2->data.info.channel_mask);
               new->request.ioa_Request.io_Unit = msg2->data.info.channel_mask & owned;
               new->request.ioa_Cycles = msg2->data.info.cycle;
               new->request.ioa_Data = msg->data.sample.start;
               new->request.ioa_Length= msg->data.sample.length;
               new->request.ioa_AllocKey = req->ioa_AllocKey;
               if (new->request.ioa_Request.io_Unit)
                  {
                  BeginIO(&new->request);
                  if (new->request.ioa_Request.io_Flags & IOF_QUICK)
                     {
                     AddTail(&queue, new);
                     if (new->request.ioa_Request.io_Error)
                        {
                        owned &=~ msg2->data.info.channel_mask;
#ifdef EXTERNAL
                        printf("Lost: %d (%d) = %d\n", new->request.ioa_Request.io_Error, msg2->data.info.channel_mask, owned);
                        fflush(stdout);
#endif
                        }
                     }
                  else
                     AddTail(&pending, new);
                  }
               else
                  AddTail(&queue, new);
               }
#ifdef EXTERNAL
            else
               printf("No new\n");
#endif
            ReplyMsg(msg2);
            }        
         break;
      case TYPE_CHANGE:
         immediate->request.ioa_Period = msg->data.info.pitch;    
         immediate->request.ioa_Volume = msg->data.info.volume;
         send_immediate(ADCMD_PERVOL, msg->data.info.channel_mask);
         break;
      case TYPE_COMM:
      case TYPE_SYNC:
      case TYPE_SYNC_DO:
         /* nothing to do there */
         break;
      default:
         /* not implemented yet */
         break;
         }
      ReplyMsg(msg);
      get_requests();
      }
   }

   
void handle_audio(struct List *l, boolean signaled)
   {
   get_requests();
   }
