/* Arch/amiga/audio.c 
	vi:ts=3 sw=3:
 */
/* $Id: audio.c,v 1.14 1995/10/13 18:00:24 espie Exp espie $
 * $Log: audio.c,v $
 * Revision 1.14  1995/10/13 18:00:24  espie
 * Source tree change.
 * Frequency is now unsigned long.
 *
 * Revision 1.13  1995/09/05 19:19:35  espie
 * *** empty log message ***
 *
 * Revision 1.12  1995/05/11  12:09:07  espie
 * Dummy call added.
 *
 * Revision 1.11  1995/02/14  16:51:22  espie
 * *** empty log message ***
 *
 * Revision 1.10  1995/02/13  22:05:42  espie
 * Implemented new allocation policy.
 * Updated calls to refer to new release.
 *
 * Revision 1.9  1995/01/13  13:31:35  espie
 * *** empty log message ***
 *
 *
 * Revision 1.6  1994/01/08  04:00:52  Espie
 * added obtain_sample, renamed release_audio_channel,
 * new_channel_tag_list.
 * Major change: moved timing computations to the audio server.
 * Pause is now feasible !
 * Better automatic time adjustement.
 * Ability to play long samples.
 * Modified task pseudoinit in case
 * we use an external server.
 * Removed some typecasts.
 * Added SYNC_DO hook facility.
 * FLUSH_BUFFER on end_all.
 * Messages now initialized correctly
 * Added external sound server for debug.
 */

#include <proto/exec.h>
#include <proto/timer.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>

#include "defs.h"
#include "extern.h"
#include "song.h"
#include "tags.h"
#include "Arch/amiga/amiga.h"

ID("$Id: audio.c,v 1.14 1995/10/13 18:00:24 espie Exp espie $")

#define AMIGA_MAX_SAMPLE_LENGTH  131072
#define AMIGA_MIN_SAMPLE_LENGTH  2

LOCAL void init_audio(void);
LOCAL void (*INIT)(void) = init_audio;

XT unsigned int inhibit_output;
struct options_set *port_options = 0;

LOCAL struct Library *TimerBase = 0;
LOCAL struct timerequest *tr = 0;

/* remember allocated samples for cleaning up in panic case */
LOCAL struct MinList tracked_list;


LOCAL void free_things()
   {
   struct MinNode *current, *next;

   SCANLIST(current, next, &tracked_list, struct MinNode *)
      FreeVec(current);
   if (TimerBase)
      CloseDevice(tr);
   if (tr)
      FreeVec(tr);
   }
      

/* kludge alert: we allocate arbitrary channel numbers around here.
 * This is probably temporary, and definitely NOT the way to go !
 */

LOCAL struct audio_channel
   {
   int amiga_number;
   struct sample_info *samp;
   int volume;
   int pitch;
   } chan[4];

LOCAL int allocated = 0;
LOCAL int left_allocated = 0;
LOCAL int right_allocated = 0;

LOCAL int free_channel[2][3] =
   {
      {0,3,-1},
      {1,2,-1}
   };

struct audio_channel *new_channel_tag_list(struct tag *prop)
   {
   struct audio_channel *new;

   INIT_ONCE;

   new = &chan[allocated];
   while (prop = get_tag(prop))
      {
      switch(prop->type)
         {
      case AUDIO_SIDE:
         switch(prop->data.scalar)
            {
         case LEFT_SIDE:
            new->amiga_number = free_channel[LEFT_SIDE][left_allocated];
            if (new->amiga_number != -1)
               left_allocated++;
            else
               return NULL;
            break;
         case RIGHT_SIDE:
            new->amiga_number = free_channel[RIGHT_SIDE][right_allocated];
            if (new->amiga_number != -1)
               right_allocated++;
            else
               return NULL;
            break;
         default:
            return NULL;
            }
         break;
      default:
         break;
         }
      prop++;
      }
         
   allocated++;
   new->samp = empty_sample();
   new->volume = 0;
   new->pitch = 0;
   new->samp = 0;
   return new;
   }

void release_audio_channels()
   {
   left_allocated = right_allocated = allocated = 0;
   }


/* Special sample management on the amiga */
void *alloc_sample(unsigned long len)
   {
   char *s;

   INIT_ONCE
   
   s = AllocVec(len + sizeof(struct MinNode), MEMF_CHIP | MEMF_CLEAR);
   if (!s)
      return 0;
   AddTail(&tracked_list, (struct Node *)s);
   return s + sizeof(struct MinNode);
   }


/* note we have to synchronize audio output before we free the sample */
#define sync_audio close_audio

void free_sample(char *s)
   {
   sync_audio();
   s -= sizeof(struct MinNode);
   Remove((struct Node *)s);
   FreeVec(s);
   }

int obtain_sample(char *start, unsigned long l, struct exfile *f)
   {
   return read_file(start, 1, l, f);
   }


LOCAL void init_timer()
   {
   int error;

   tr = AllocVec(sizeof(struct timerequest), MEMF_PUBLIC | MEMF_CLEAR);
   if (!tr)
      end_all("No timerequest");
   error = OpenDevice(TIMERNAME, UNIT_MICROHZ, tr, 0);
   if (error)
      end_all("Could not open timer");
   TimerBase = (struct Library *)tr->tr_node.io_Device;
   }


LOCAL void init_audio()
   {
   NewList(&tracked_list);
   at_end(free_things);
   init_timer();
   }

/* indispensible for not losing memory ! */

void __regargs __chkabort()
   {
   }
   
/* audio */

/* empty operation on the amiga */
void init_tables(oversample, frequency)
int oversample;
unsigned long frequency;
   {
   }

void set_data_width(n, p)
int n, p;
   {
   }

void resample(oversample, number)
int oversample;
int number;
   {
   struct ext_message *msg;   

   INIT_ONCE;
   msg = obtain_message();
   msg->data.time.low = (unsigned long)number;
   msg->data.time.high = 0;
   send(msg, TYPE_WAIT);
   }


/* So you can play long samples with short repeat parts */
LOCAL void play_long_sample(struct audio_channel *au, UBYTE *start, int length)
   {
   struct ext_message *msg, *msg2;
   int chunk;
   while (length >= AMIGA_MIN_SAMPLE_LENGTH)
      {
      chunk = MIN(AMIGA_MAX_SAMPLE_LENGTH, length);
      msg = obtain_message();
      msg2 = obtain_message();
      msg->data.sample.start = start;
      msg->data.sample.length = chunk;
      msg2->data.info.channel_mask = 1<<au->amiga_number;
      msg2->data.info.cycle = 1;
      msg2->data.info.pitch = au->pitch;
      msg2->data.info.volume = au->volume;
      Forbid();
      send(msg, TYPE_SETUP);
      send(msg2, TYPE_CHANGE);
      Permit();
      length -= chunk;
      start += chunk;
      }
   }

void play_note(au, samp, pitch)
struct audio_channel *au;
struct sample_info *samp;
int pitch;
   {
   struct ext_message *msg, *msg2;

   au->pitch = pitch;
   msg = obtain_message();
   msg->data.info.channel_mask = 1<<au->amiga_number;
   send(msg, TYPE_FLUSH_CHANNEL);
   
   if (samp)
      {
      au->samp = samp;
      if (samp->start)
         {
         play_long_sample(au, samp->start, samp->length);
         if (samp->rp_start)
            {
            msg = obtain_message();
            msg2 = obtain_message();
            msg->data.sample.start = samp->rp_start;
            msg->data.sample.length = samp->rp_length;
            msg2->data.info.channel_mask = 1<<au->amiga_number;
            msg2->data.info.cycle = 0;
            msg2->data.info.pitch = 0;
            msg2->data.info.volume = 0;
            Forbid();
            send(msg, TYPE_SETUP);
            send(msg2, TYPE_CHANGE);
            Permit();
            }  
         }
      }
   }

void set_play_pitch(au, pitch)
struct audio_channel *au;
int pitch;
   {
   if (pitch != au->pitch)
      {
      struct ext_message *msg;

      au->pitch = pitch;
      msg = obtain_message();
      msg->data.info.channel_mask = 1<<au->amiga_number;
      msg->data.info.pitch = au->pitch;
      msg->data.info.volume = au->volume;
      send(msg, TYPE_CHANGE);
      }
   }

void set_play_volume(au, volume)
struct audio_channel *au;
int volume;
   {
   if (volume != au->volume)
      {
      struct ext_message *msg;

      au->volume = volume;
      if (au->samp != empty_sample())
         {
         msg = obtain_message();
         msg->data.info.channel_mask = 1<<au->amiga_number;
         msg->data.info.pitch = au->pitch;
         msg->data.info.volume = au->volume;
         send(msg, TYPE_CHANGE);
         }
      }
   }

void set_play_position(au, pos)
struct audio_channel *au;
int pos;
   {
   struct ext_message *msg, *msg2;
   
   
   msg = obtain_message();
   msg->data.info.channel_mask = 1<<au->amiga_number;
   send(msg, TYPE_FLUSH_CHANNEL);

   if (au->samp->start)
      {
      if (pos < au->samp->length)
         {
         play_long_sample(au, au->samp->start + pos, au->samp->length - pos);
         if (au->samp->rp_start)
            {
            msg = obtain_message();
            msg2 = obtain_message();
            msg->data.sample.start = au->samp->rp_start;
            msg->data.sample.length = au->samp->rp_length;
            msg2->data.info.channel_mask = 1<<au->amiga_number;
            msg2->data.info.cycle = 0;
            msg2->data.info.pitch = 0;
            msg2->data.info.volume = 0;
            Forbid();
            send(msg, TYPE_SETUP);
            send(msg2, TYPE_CHANGE);
            Permit();
            }  
         }
      }
   }



void set_mix(percent)
int percent;
   {
   }

unsigned long open_audio(f, s)
unsigned long f;
int s;
   {
   ULONG freq;
   LOCAL struct EClockVal dummy;

   INIT_ONCE;

      /* samples/sec used as a timing unit: 1sec =1 000 000 µs */
   freq = ReadEClock(&dummy);
   return (unsigned long)freq;
   }
   
void set_synchro(s)
int s;
   {
   }

unsigned long update_frequency()
   {
   return 0;
   }

void flush_buffer(void)
   {
   }

void discard_buffer(void)
   {
   struct ext_message *msg;
   
   inhibit_output++;
   msg = obtain_message();
   send(msg, TYPE_FLUSH_BUFFER);
   msg = obtain_message();
   send(msg, TYPE_SYNC);
   while (msg != await_type(TYPE_SYNC))
      ;
   inhibit_output--;
   }

