/* support functions for yours truly amiga 
	vi:se ts=3 sw=3:
 */

/* $Id: amiga_support.c,v 1.4 1993/12/04 16:12:50 espie Exp espie $ */
/* $Log: amiga_support.c,v $
 * Revision 1.4  1993/12/04  16:12:50  espie
 * BOOL -> boolean.
 * Got rid of channel in resample.
 *
 * Revision 1.3  1993/12/02  15:45:33  espie
 * Stupid fix + type casts.
 *
 * Revision 1.2  1993/11/17  15:29:53  espie
 * Added higher-level support. Separated plainly from audio.c
 * */

#include <stdio.h>
#include "defs.h"
#include "extern.h"

#include "song.h"
#include "channel.h"

#include <hardware/cia.h>
#include <hardware/intbits.h>
#include <hardware/dmabits.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <devices/audio.h>
#include <devices/timer.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/timer.h>
#undef X
#include <proto/intuition.h>
#include <proto/gadtools.h>

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

LOCAL struct sample_info dummy =
	{
	NULL,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	NULL,
	NULL
	};

LOCAL int allocated = 0;

struct audio_channel *new_channel()
	{
	struct audio_channel *new;

	new = &chan[allocated];
	new->amiga_number = allocated++;
	new->samp = &dummy;
	new->volume = 0;
	new->pitch = 0;
	new->samp = 0;
	return new;
	}

void no_audio_channels()
	{
	allocated = 0;
	}

/* list scanning.
 * next is needed: with it, we can actually unlink the node while scanning
 *	the current list.  Type is provided to avoid type-casting errors in
 * SCANLIST expansion.
 */
#define SCANLIST(node, next, list, type) \
	for((node) = (type)((struct MinList *)(list))->mlh_Head; \
		(next) = (type)((struct MinNode *)(node))->mln_Succ; \
		(node) = (next))

LOCAL char *version = "\0$VER: tracker 3.11";

/* remember allocated samples for cleaning up in panic case */
LOCAL struct MinList tracked_list;
/* timer variables:
 *		the message port 
 */
LOCAL struct MsgPort *tport = 0;
/*		the basic wait request */
LOCAL struct timerequest *tr = 0;
/* 		the reference system time at which the song started */
LOCAL struct EClockVal system_time;

LOCAL struct Library *TimerBase;

/* Clean up: Do we need to close timer ? */
LOCAL boolean timer_opened = FALSE;
/* Not really useful */
LOCAL boolean first_time = FALSE;
extern volatile struct CIA __far ciaa;

LOCAL void init_timer()
	{
	int fail;
	tport = CreateMsgPort();
	if (!tport)
		{
		exit(10);
		}
	tr = CreateIORequest(tport, sizeof(struct timerequest));
	if (!tr)
		{
		exit(10);
		}
	fail = OpenDevice(TIMERNAME, UNIT_WAITECLOCK, (struct IORequest *)tr, 0);
	if (fail)
		exit(10);
	else
		timer_opened = TRUE;
	TimerBase = tr->tr_node.io_Device;
	}

/* The basic user interface (a simple window) */
LOCAL struct IntuitionBase *IntuitionBase = 0;
LOCAL struct Library *GadtoolsBase = 0;
LOCAL struct Window *win;
/* We use topaz.font so as not to get into sizing problems */
LOCAL struct TextAttr topaz =
	{
	"topaz.font",
	8,
	0,
	0
	};

LOCAL struct NewGadget template =
	{
	10, 10, 	/* start position (offset from left/topedge) */
	72, 20,		/* width/height */
	NULL,
	&topaz,
	0,			/* gadget ID */
	0,
	NULL,
	NULL
	};

LOCAL APTR vi;
LOCAL struct Screen *pub = 0;
LOCAL struct Gadget *glist;

/* we precisely have seven gadgets */
#define MAX_GADGET 7

/* all labelled with strings */
LOCAL char *label[MAX_GADGET] =
	{
	"Previous",
	"Next",
	"Restart",
	"<<",
	">>", 
	"NTSC",
	"PAL",
	};

/* and mapping to these commands of the normal tracker interface */
LOCAL char mapto[MAX_GADGET] = { 'p', 'n', 'r', '<', '>', 'S', 's' };
	
LOCAL void init_ui(void)
	{
	struct Gadget *gad;
	int i;

	IntuitionBase = OpenLibrary("intuition.library", 37);
	if (!IntuitionBase)
		exit(10);
	GadtoolsBase = OpenLibrary("gadtools.library", 37);
	if (!GadtoolsBase)
		exit(10);
	pub = LockPubScreen(NULL);
	if (!pub)
		exit(10);
	vi = GetVisualInfo(pub, TAG_END);
	if (!vi)
		exit(10);
	gad = CreateContext(&glist);
	if (!gad)
		exit(10);
	template.ng_VisualInfo = vi;
		/* set up Top/Left Edge of initial gadget according to Wbar */
	template.ng_TopEdge += pub->WBorTop + pub->Font->ta_YSize + 1;
	template.ng_LeftEdge += pub->WBorLeft;	/* which I forgot... */
		/* lay out gadgets */
	for (i = 0; i < MAX_GADGET; i++)
		{
		template.ng_GadgetText = label[i];
		gad = CreateGadget(BUTTON_KIND, gad, &template, TAG_END);
		if (!gad)
			exit(10);
		template.ng_LeftEdge += template.ng_Width + 5;
		template.ng_GadgetID++;
		}
		
	win = OpenWindowTags(NULL, 
		WA_Title, "Experiment IV",
		WA_Width, template.ng_LeftEdge + 10,	
		WA_Height, template.ng_Height + template.ng_TopEdge + 10,
		WA_MouseQueue, 35,	/* we can't always answer messages */
		WA_DepthGadget, TRUE,
		WA_CloseGadget, TRUE,
		WA_DragBar, TRUE,
		WA_SmartRefresh, TRUE,	/* don't want to be bothered with refresh */
		WA_Gadgets, glist,
		WA_IDCMP, IDCMP_CLOSEWINDOW | BUTTONIDCMP | IDCMP_REFRESHWINDOW,
		TAG_DONE, 0);
	if (!win)
		exit(10);
	GT_RefreshWindow(win, NULL);	
	}


/* the audio interface */

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

LOCAL struct ext_audio 
	{
	struct IOAudio request;
	struct ext_audio *next;
	} 
/* the audio queue */	
	*first = 0, 
/* the specific request that get used for closing/opening */
	*req = 0;

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

LOCAL void command_audio(struct ext_audio *io);
LOCAL void send_immediate(ULONG command, int mask);

LOCAL UBYTE whichchannel[] = {15};

/* allocate_channels(): sends a request to the audio.device for
 * the channels.
 */
LOCAL void allocate_channels(void)
   { 
   struct ext_audio *sweep;

   req->request.ioa_Request.io_Command = ADCMD_ALLOCATE;
   req->request.ioa_AllocKey = 0;
   req->request.ioa_Data = whichchannel;
   req->request.ioa_Length = sizeof(whichchannel);
   command_audio(req);

   for (sweep = first; sweep; sweep = sweep->next)
      sweep->request.ioa_AllocKey = req->request.ioa_AllocKey;
   }
    
/* free_channels(): give back the channels.
 */ 
LOCAL void free_channels(void)
   {
   req->request.ioa_Request.io_Command = ADCMD_FREE;
   command_audio(req);
   }


LOCAL struct ext_audio *create_request(void)
   {
   struct ext_audio *new;

   new = AllocMem(sizeof(struct ext_audio), MEMF_CLEAR|MEMF_PUBLIC);
   if (req)
      *new = *req;
   return new;
   }

/* creates a whole queue of audio requests */   
LOCAL void create_queue(void)
   {
   struct ext_audio *last, *new;
   int i;
   
   last = NULL;
   for (i = 0; i < QUEUE_LENGTH; i++)
      {
      new = create_request();
      new->next = last;
      last = new;
      }
   first = new;
   }

LOCAL void get_requests(void)
   {
   struct ext_audio *back;

   while(back = GetMsg(port))
   	if (back != req)		/* only those belonging to the queue */
	      {
   	   back->next = first;
      	first = back;
	      }
   }

LOCAL void send_immediate(ULONG command, int mask)
   {
   first->request.ioa_Request.io_Command = command;
   first->request.ioa_Request.io_Flags = IOF_QUICK;
   first->request.ioa_Request.io_Unit = mask;
   BeginIO((struct IORequest *)first);
   }

LOCAL void command_audio(struct ext_audio *io)
   {
   struct ext_audio *request;

   BeginIO((struct IORequest *)io);
   WaitPort(port);
	get_requests();
   }

LOCAL void reset_audio(void)
   {
   send_immediate(CMD_RESET, 15);
   get_requests();
   }

/* obtain_audio(): allocate all the structures we will need to
 * play with the audio device
 */ 
LOCAL void obtain_audio(void)
   {
   BYTE fail;
   
   port = CreateMsgPort();
   if (!port)
   	{
   	fprintf(stderr, "Couldn't allocate port");
		exit(10);
		}
   req = CreateIORequest(port, sizeof(struct ext_audio));
   if (!req)
   	{
   	fprintf(stderr, "Couldn't allocate io request");
   	exit(10);
		}
   req->request.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)
		{
		fprintf(stderr, "Couldn't open audio device");
		exit(10);
		}
	else
		audio_opened = TRUE;
   }

/* remember old task priority at exit (CLI process runs on the CLI context,
 * and reverts to being a shell with the same priority on exit
 */
LOCAL int oldpri;

LOCAL void amiga_init()
	{
	oldpri = SetTaskPri(FindTask(0), 15);
		/* make cursor invisible, speed up output some */
	printf("\233""0 p\n");
	obtain_audio();
	create_queue();
	allocate_channels();
	init_timer();
		/* audio filter OFF */
	ciaa.ciapra |= CIAF_LED;
	init_ui();
	}

LOCAL void amiga_cleanup()
	{
	struct MinNode *current, *next;
	
	SetTaskPri(FindTask(0), oldpri);
	printf("\233"" p\n");
	if (win)
		CloseWindow(win);
	if (glist)
		FreeGadgets(glist);
	if (vi)
		FreeVisualInfo(vi);
	if (pub)
		UnlockPubScreen(NULL, pub);
	if (IntuitionBase)
		CloseLibrary((struct Library *)IntuitionBase);
	if (GadtoolsBase)
		CloseLibrary(GadtoolsBase);
	SCANLIST(current, next, &tracked_list, struct MinNode *)
		FreeVec(current);
   if (req->request.ioa_AllocKey)
		free_channels();
	if (audio_opened)
		CloseDevice((struct IORequest *)req);
	if (req)
		DeleteIORequest(req);
	if (port)
		DeletePort(port);
	while(first)
		{
		struct ext_audio *temp;
		
		temp = first;
		first = first->next;
		FreeMem(temp, sizeof(struct ext_audio));
		}

	if (timer_opened)
		{
		if (!CheckIO((struct IORequest *)tr))
			{
			AbortIO((struct IORequest *)tr);
			WaitIO((struct IORequest *)tr);
			}
		CloseDevice((struct IORequest *)tr);
		}
	if (tr)
		DeleteIORequest(tr);
	if (tport)
		DeleteMsgPort(tport);
	ciaa.ciapra &= ~CIAF_LED;
	}

/* indispensible for not losing memory ! */

void __regargs __chkabort()
	{
	}
	
void *alloc_sample(int len)
	{
	char *s;
	
	s = AllocVec(len + sizeof(struct MinNode), MEMF_CHIP | MEMF_CLEAR);
	if (!s)
		return 0;
	AddTail((struct List *)&tracked_list, (struct Node *)s);
	return s + sizeof(struct MinNode);
	}

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

/* termio */
boolean run_in_fg()
	{
	return 1;
	}

void nonblocking_io()
	{
	static boolean yet = 0;
	if (!yet)
		{
		NewList((struct List *)&tracked_list);
		atexit(amiga_cleanup);
		amiga_init();
		yet = 1;
		}
	}

void sane_tty()
	{
	}
	
int may_getchar()
	{
	struct IntuiMessage *msg;
	int id;
	if (SetSignal(0,0) & SIGBREAKF_CTRL_C)
		exit(10);
	while(msg = GT_GetIMsg(win->UserPort))
		switch(msg->Class)
			{
		case IDCMP_CLOSEWINDOW:
			GT_ReplyIMsg(msg);
			return 'q';
		case IDCMP_REFRESHWINDOW:
			GT_ReplyIMsg(msg);
			GT_BeginRefresh(win);
			GT_EndRefresh(win, TRUE);
			break;
		case IDCMP_GADGETUP:
			id = ((struct Gadget *)msg->IAddress)->GadgetID;
			GT_ReplyIMsg(msg);
			return mapto[id];
		default:
			GT_ReplyIMsg(msg);
			}
	return -1;
	}

/* audio */

/* empty operation on the amiga */
void init_tables(oversample, frequency, chan)
int oversample, frequency;
struct channel *chan;
	{
	}

void resample(oversample, number)
int oversample;
int number;
	{
	int i;
/*
	if (first_time)
		{
		first_time = FALSE;
		system_time.tv_micro += number;
		}
*/
		tr->tr_node.io_Command = TR_ADDREQUEST;
		tr->tr_time.tv_secs = system_time.ev_hi;
		tr->tr_time.tv_micro = system_time.ev_lo;
		SendIO((struct IORequest *)tr);
		WaitPort(tport);
		while(GetMsg(tport))
			;
	system_time.ev_lo += (unsigned long)number;
	if (system_time.ev_lo < (unsigned long)number)
		system_time.ev_hi++;
	}

void play_note(au, samp, pitch)
struct audio_channel *au;
struct sample_info *samp;
int pitch;
	{
	au->pitch = pitch;
	send_immediate(CMD_FLUSH, 1 << au->amiga_number);
	get_requests();
	if (samp)
		{
		au->samp = samp;
		if (au->samp->start)
			{
			first->request.ioa_Request.io_Command = CMD_WRITE;
			first->request.ioa_Request.io_Flags = ADIOF_PERVOL;
			first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
			first->request.ioa_Data = au->samp->start;
			first->request.ioa_Length = au->samp->length;
			first->request.ioa_Period = au->pitch;
			first->request.ioa_Volume = au->volume;
			first->request.ioa_Cycles = 1;
			BeginIO((struct IORequest *)first);
			first = first->next;
			if (au->samp->rp_start)
				{
				first->request.ioa_Request.io_Command = CMD_WRITE;
				first->request.ioa_Request.io_Flags = 0;
				first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
				first->request.ioa_Data = au->samp->rp_start;
				first->request.ioa_Length = au->samp->rp_length;
				first->request.ioa_Cycles = 0;
				BeginIO((struct IORequest *)first);
				first = first->next;
				}  
			}
		}
	}

void set_play_pitch(au, pitch)
struct audio_channel *au;
int pitch;
	{
	if (pitch != au->pitch)
		{
		au->pitch = pitch;
		first->request.ioa_Period = pitch;
		first->request.ioa_Volume = au->volume;
		send_immediate(ADCMD_PERVOL, 1 << au->amiga_number);
		}
	}

void set_play_volume(au, volume)
struct audio_channel *au;
int volume;
	{
	if (volume != au->volume)
		{
		au->volume = volume;
		first->request.ioa_Period = au->pitch;
		first->request.ioa_Volume = volume;
		send_immediate(ADCMD_PERVOL, 1 << au->amiga_number);
		}
	}

void set_play_position(au, pos)
struct audio_channel *au;
int pos;
	{
	send_immediate(CMD_FLUSH, 1 << au->amiga_number);
	get_requests();
	if (au->samp->start)
		{
		if (pos < au->samp->length)
			{
			first->request.ioa_Request.io_Command = CMD_WRITE;
			first->request.ioa_Request.io_Flags = ADIOF_PERVOL;
			first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
			first->request.ioa_Data = au->samp->start + pos;
			first->request.ioa_Length = au->samp->length - pos;
			first->request.ioa_Period = au->pitch;
			first->request.ioa_Volume = au->volume;
			first->request.ioa_Cycles = 1;
			BeginIO((struct IORequest *)first);
			first = first->next;
			if (au->samp->rp_start)
				{
				first->request.ioa_Request.io_Command = CMD_WRITE;
				first->request.ioa_Request.io_Flags = 0;
				first->request.ioa_Request.io_Unit = 1<<au->amiga_number;
				first->request.ioa_Data = au->samp->rp_start;
				first->request.ioa_Length = au->samp->rp_length;
				first->request.ioa_Cycles = 0;
				BeginIO((struct IORequest *)first);
				first = first->next;
				}  
			}
		}
	}


void set_mix(percent)
int percent;
	{
	}

int open_audio(f, s)
int f, s;
	{
	first_time = TRUE;
	return ReadEClock(&system_time);		/* samples/sec used as a timing unit: 1sec =1 000 000 µs */
	}
	
void set_synchro(s)
boolean s;
	{
	ReadEClock(&system_time);
	first_time = TRUE;
	}

int update_frequency()
	{
	return 0;
	}

void output_samples(int left, int right)
	{
	}
	
void flush_buffer(void)
	{
	}

void discard_buffer(void)
	{
	reset_audio();
	}
	
void close_audio(void)
	{
	}
	

	
/* amiga: popen() for OS2_04 */


#include <proto/dos.h>
#include <dos/dostags.h>
#include <stdio.h>
#include <string.h>

/*
###	CSupport/popen
###
###	NAME
###		popen/pclose -- Unix-like pipes
###
###	STATUS
###		Experimental
###
 */
FILE *popen(char *command, char *mode)
	{
	
	if (strcmp(mode, "r") == 0)
		/* open pipe for reading */
		{
		FILE *reader;
		BPTR writer, null;

		writer = Open("PIPE:", MODE_NEWFILE);
		reader = fopen("PIPE:", "r");
		null = Open("NIL:", MODE_NEWFILE);
		if (SystemTags(command, SYS_Input, null, 
			SYS_Output, writer, SYS_Asynch, TRUE, TAG_END) == -1)
			{
			Close(null);
			Close(writer);
			fclose(reader);
			return NULL;
			}
		else
			return reader;
		}
	else if (strcmp(mode, "w") == 0)
		/* open pipe for writing */
		{
		FILE *writer;
		BPTR reader, null;
		
		writer = fopen("PIPE:", "w");
		reader = Open("PIPE:", MODE_OLDFILE);
		null = Open("NIL:", MODE_NEWFILE);
		if (SystemTags(command, SYS_Input, reader, 
			SYS_Output, null, SYS_Asynch, TRUE, TAG_END) == -1)
			{
			Close(null);
			Close(reader);
			fclose(writer);
			return NULL;
			}
		else
			return writer;
		}
	else
		return NULL;
	}

/* for us, pclose is just fclose.
 * We hope everything will close out alright
 */
void pclose(FILE *f)
	{
	fclose(f);
	}

