/*
Copyright (C) 2000 Peter McGavin.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// snd_amiga.c

#include <exec/exec.h>
#include <dos/dos.h>
#include <graphics/gfxbase.h>
#include <devices/audio.h>

#if defined(__STORM__) && defined(__PPC__)
#include <powerpc/powerpc.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/graphics_protos.h>
#include <clib/powerpc_protos.h>
extern struct GfxBase *GfxBase;
#else
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#endif

#include "quakedef.h"

#if defined(__SASC) && defined(__PPC__)
#define	BeginIO(ioRequest)	_BeginIO(ioRequest)
__inline void
_BeginIO(struct IORequest *ioRequest)
{
struct Caos	MyCaos;
	MyCaos.M68kCacheMode	=	IF_CACHEFLUSHALL;
//	MyCaos.M68kStart	=	NULL;
//	MyCaos.M68kSize		=	0;
	MyCaos.PPCCacheMode	=	IF_CACHEFLUSHALL;
//	MyCaos.PPCStart		=	NULL;
//	MyCaos.PPCSize		=	0;
	MyCaos.a1		=(ULONG) ioRequest;
	MyCaos.caos_Un.Offset	=	(-30);
	MyCaos.a6		=(ULONG) ioRequest->io_Device;
	PPCCallOS(&MyCaos);
}
#endif

#if defined(__STORM__) && defined(__PPC__)
#define	BeginIO(ioRequest) _BeginIO(ioRequest)
__inline void
_BeginIO(struct IORequest *ioRequest)
{
struct PPCArgs	args;
	memset(&args,0,sizeof(args));
	args.PP_Code		=(APTR) ioRequest->io_Device;
	args.PP_Offset		=	(-30);
	args.PP_Flags		=	0;
	args.PP_Regs[PPREG_A1]	=(ULONG) ioRequest;
	args.PP_Regs[PPREG_A6]	=(ULONG) ioRequest->io_Device;
	Run68K (&args);
}
#endif

/**********************************************************************/

extern int desired_speed;
extern int desired_bits;

/**********************************************************************/

#define MAXNUMCHANNELS   4   /* max number of Amiga sound channels */
/* #define BUFFERSIZE 16384 */
#define BUFFERSIZE 4096

struct channel_info {
  struct MsgPort *audio_mp;
  struct IOAudio *audio_io;
  double starttime;
  BOOL sound_in_progress;
};

static struct channel_info channel_info[MAXNUMCHANNELS] = {
  {NULL, NULL, FALSE},
  {NULL, NULL, FALSE},
  {NULL, NULL, FALSE},
  {NULL, NULL, FALSE},
};

static int size;

static struct MsgPort *audio_mp = NULL;
static struct IOAudio *audio_io = NULL;
static BOOL audio_is_open = FALSE;
static ULONG clock_constant;   /* see Amiga Hardware Manual page 141 */
static UWORD period;
static double twice_real_speed;

/**********************************************************************/
// Stops a sound channel.

static void stopsound (int cnum)
{
  if (!audio_is_open)
    return;
  if (channel_info[cnum].sound_in_progress) {
    AbortIO ((struct IORequest *)channel_info[cnum].audio_io);
    WaitPort (channel_info[cnum].audio_mp);
    GetMsg (channel_info[cnum].audio_mp);
    channel_info[cnum].sound_in_progress = FALSE;
  }
}

/**********************************************************************/
// Starts a sound in a particular sound channel.

static int startsound (int cnum, char *buffer, int length)
{
  struct channel_info *c;

  if (!audio_is_open)
    return 1;
  stopsound (cnum);
  c = &channel_info[cnum];
  c->audio_io->ioa_Request.io_Command = CMD_WRITE;
  c->audio_io->ioa_Request.io_Flags = ADIOF_PERVOL;
  c->audio_io->ioa_Data = buffer;
  c->audio_io->ioa_Length = length;
  c->audio_io->ioa_Period = period;
  c->audio_io->ioa_Volume = 64;
  c->audio_io->ioa_Cycles = 0;
  BeginIO ((struct IORequest *)c->audio_io);
  c->starttime = Sys_FloatTime ();
  c->sound_in_progress = TRUE;
  return cnum;
}

/**********************************************************************/

qboolean SNDDMA_Init (void)
{
  int i;
  struct channel_info *c;
  UBYTE chans[1];

//  printf ("SNDDMA_Init()\n");

  if ((shm = (dma_t *)malloc (sizeof(dma_t))) == NULL)
    Sys_Error ("malloc() failed");
  memset((void*)shm, 0, sizeof(dma_t));

  if ((shm->buffer = AllocMem (BUFFERSIZE, MEMF_CHIP | MEMF_CLEAR)) == NULL)
    Sys_Error ("Out of CHIP memory for sound");
//  memset(shm->buffer, 0x80, BUFFERSIZE);

//  printf ("Sound buffer at 0x%08x\n", shm->buffer);

  shm->channels = 2;
  shm->speed = desired_speed;
  shm->samplebits = 8;
  shm->samples = BUFFERSIZE / (shm->samplebits / 8);
  shm->submission_chunk = 1;

  if ((audio_mp = CreateMsgPort ()) == NULL ||
      (audio_io = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),
                                           MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
    Sys_Error ("CreateMsgPort() or AllocMem() failed");

  chans[0] = (1 << shm->channels) - 1; /* shm->channels Amiga audio channels */
  audio_io->ioa_Request.io_Message.mn_ReplyPort = audio_mp;
  audio_io->ioa_Request.io_Message.mn_Node.ln_Pri = 127;
  audio_io->ioa_AllocKey = 0;
  audio_io->ioa_Data = chans;
  audio_io->ioa_Length = sizeof(chans);

  if (OpenDevice (AUDIONAME, 0, (struct IORequest *)audio_io, 0) != 0)
    Sys_Error ("OpenDevice(\"audio.device\") failed");
  audio_is_open = TRUE;

  for (i = 0; i < shm->channels; i++) {
    c = &channel_info[i];
    if ((c->audio_mp = CreateMsgPort ()) == NULL ||
        (c->audio_io = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),
                                           MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
      Sys_Error ("CreateMsgPort() or AllocMem() failed");
    *c->audio_io = *audio_io;
    c->audio_io->ioa_Request.io_Message.mn_ReplyPort = c->audio_mp;
    c->audio_io->ioa_Request.io_Unit = (struct Unit *)(1 << i);
  }

  if ((GfxBase->DisplayFlags & REALLY_PAL) == 0)
    clock_constant = 3579545;   /* NTSC */
  else
    clock_constant = 3546895;   /* PAL */

  period = ((clock_constant << 1) + shm->speed) / ((shm->speed) << 1);

  twice_real_speed = 2.0 * ((double)clock_constant) / (double)period;

  startsound (0, shm->buffer, BUFFERSIZE >> 1);
  startsound (1, shm->buffer + (BUFFERSIZE >> 1), BUFFERSIZE >> 1);

  return 1;
}

/**********************************************************************/

int SNDDMA_GetDMAPos (void)
{
  if (shm == NULL || shm->buffer == NULL || !channel_info[0].sound_in_progress)
    return 0;

  shm->samplepos = ((int)((Sys_FloatTime() - channel_info[0].starttime)
                          * twice_real_speed + 0.5))
                   & (BUFFERSIZE - 1);

//  shm->samplepos = (int)(realtime*shm->speed*shm->channels) & (shm->samples-1);

//  if (pos < 0 || pos >= BUFFERSIZE)
//    Sys_Error ("pos = %d is out of range\n");
  return shm->samplepos;
}

/**********************************************************************/

void SNDDMA_Shutdown (void)
{
  int i;

//  printf ("SNDDMA_Shutdown()\n");

  if (audio_is_open) {
    if (shm != NULL) {
      for (i = 0; i < shm->channels; i++)
        stopsound (i);
      audio_io->ioa_Request.io_Unit = (struct Unit *)
                       ((1 << shm->channels) - 1);  /* free shm->channels channels */
    }
    CloseDevice ((struct IORequest *)audio_io);
    audio_is_open = FALSE;
  }
  for (i = 0; i < MAXNUMCHANNELS; i++) {
    if (channel_info[i].audio_io != NULL) {
      FreeMem (channel_info[i].audio_io, sizeof(struct IOAudio));
      channel_info[i].audio_io = NULL;
    }
    if (channel_info[i].audio_mp != NULL) {
      DeleteMsgPort (channel_info[i].audio_mp);
      channel_info[i].audio_mp = NULL;
    }
  }
  if (audio_io != NULL) {
    FreeMem (audio_io, sizeof(struct IOAudio));
    audio_io = NULL;
  }
  if (audio_mp != NULL) {
    DeleteMsgPort (audio_mp);
    audio_mp = NULL;
  }
  if (shm != NULL) {
    if (shm->buffer != NULL) {
      FreeMem (shm->buffer, BUFFERSIZE);
      shm->buffer = NULL;
    }
    free ((char *)shm);
    shm = NULL;
  }
}

void SNDDMA_Submit (void)
{
//  printf ("SNDDMA_Submit()\n");
}

/**********************************************************************/

#ifdef __SASC
void _STD_SNDDMA_Shutdown (void)
{
//  printf ("_STD_SNDDMA_Shutdown()\n");

  SNDDMA_Shutdown ();
}
#endif

/**********************************************************************/
#ifdef __STORM__
void EXIT_9_SNDDMA_Shutdown (void)
{
//  printf ("EXIT_9_SNDDMA_Shutdown()\n");

  SNDDMA_Shutdown ();
}
#endif

/**********************************************************************/
#ifdef __VBCC__
void _EXIT_9_SNDDMA_Shutdown (void)
{
//  printf ("_EXIT_9_SNDDMA_Shutdown()\n");

  SNDDMA_Shutdown ();
}
#endif

/**********************************************************************/
