/* MIDICLOCK, Pete Yadlowsky, 3/89

	This program is based loosely on a simple CIA timer application
	originally written by Paul Higginbotham (sp?) and placed in the
	public domain. Thanks, Paul.

	The function of this program is to provide a time counter that
	any task may access, usually for the purpose of calculating
	time elapsed between events. This program was written to
	facilitate the recording of MIDI events and to allow several
	MIDI processes to share a common timebase, but is general enough
	to be useful in a wide range of applications.

	The program sets up a CIA timer interrupt in order to produce
	a number of 'ticks' per second (specified on command line or
	in message), and to simply count these ticks. The current
	tick count, along with the current rate (actually, microseconds
	per tick), is stored in a simple structure whose address is
	placed in the tc_UserData field of the timer process's task
	structure. A task that wishes to get access to this data need
	only find the timer task (called 'MidiClock') and set a pointer
	to the aforementioned structure (found in midiclock.h).

	When midiclock is first invoked, an optional ticks/sec value
	(default is 120) is read from the command line and the timer
	process is started. Further invocations can be made to message
	the running timer to either change its rate or terminate:

		midiclock 240	changes the rate to 240 ticks/sec
		midiclock -q	terminates the timer

	Next on the list of projects is to have the timer task send
	wake-up messages (one-shot or continuous) to interested
	tasks, typically for MIDI playback or sync. This is becoming
	a sort of specialized timer.device, without a lot of the
	overhead.

	If you pass this source code on, please try to keep
	'midiclock.h' with it.
*/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/cia.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>
#include <resources/cia.h>
#include <stdio.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include "midiclock.h"

extern struct CIA ciab;
struct mc_Msg *GetMsg();

static struct Interrupt
   CIATimerInterrupt,
   *OldCIAInterrupt = (struct Interrupt *)-1;

struct MidiClockData time;

static struct Library *CIAResource = NULL;

/* using CIAB timer B */

#define ciatlo ciab.ciatblo
#define ciathi ciab.ciatbhi
#define ciacr ciab.ciacrb
#define CIAINTBIT CIAICRB_TB
#define CLEAR 0
#define DEFAULT_TICKS 120
#define NOMSG -1
#define KILL 0
#define RATE 1
#define MC_PRIORITY 30

void CIAInterrupt()
{
	time.counter++;
}

/* start the timer, clear pending interrupts, and enable timer B Interrupts */
StartCIATimer()
{
	ciacr &= ~(CIACRBF_RUNMODE);	/* set it to reload on overflow */
	ciacr |= (CIACRBF_LOAD | CIACRBF_START);
	SetICR(CIAResource,CLEAR|CIAICRF_TB);
	AbleICR(CIAResource, CIAICRF_SETCLR | CIAICRF_TB);
	return 0;
}

void StopCIATimer()
{
	AbleICR(CIAResource, CLEAR | CIAICRF_TB);
	ciacr &= ~CIACRBF_START;
}

void SetCIATimer (ticks) /* ticks/second converted to usecs */
int ticks;
{
	long micros;

	micros = (10000000 / ticks + 5) / 10;	
	time.intrvl = micros;
	micros = (micros * 10000 / 1397 + 5) / 10; /* 1.397 usecs/tick */
	ciatlo = micros & 0xff;
	ciathi = micros >> 8;
}

void EndCIATimer()
{
	if (OldCIAInterrupt == NULL)
	{
		StopCIATimer ();
		RemICRVector (CIAResource, CIAINTBIT, &CIATimerInterrupt);
	}
}

int BeginCIATimer (ticks)
int ticks;
{
	/* Open the CIA resource */
	if ((CIAResource = (struct Library *)OpenResource(CIABNAME)) == NULL)
	{
		return(1);
	}

	CIATimerInterrupt.is_Node.ln_Type = NT_INTERRUPT;
	CIATimerInterrupt.is_Node.ln_Pri = 127;
	CIATimerInterrupt.is_Node.ln_Name =  "CIA_MIDI_TIMER";
	CIATimerInterrupt.is_Code = CIAInterrupt;
	CIATimerInterrupt.is_Data = (APTR) &time;

	/* install interrupt */
	if ((OldCIAInterrupt = AddICRVector(CIAResource,CIAINTBIT,&CIATimerInterrupt)) != NULL)
	{
		EndCIATimer ();
		return (2);
	}

	SetCIATimer (ticks);
	StartCIATimer ();
	return (0);
}

main (argc, argv)
int argc;
char *argv[];
{
	int i, error, quit, ticks, oldpri;
	char *pt, *oldname;
	struct Process *FindTask(), *timerproc;
	struct MsgPort *rpyport, *myport, *timerport, *CreatePort();
	struct mc_Msg *msg, ctlmsg;

	/* If timer process is not found, make this the timer process */

	if ((timerproc = FindTask (MIDICLOCKNAME)) == NULL) {
		if (argc > 1) ticks = atoi (argv[1]); /* command line arg */
		else ticks = DEFAULT_TICKS;
		if (error = BeginCIATimer (ticks)) { /* set up interrupt */
			printf ("MIDICLOCK: error starting CIA timer.\n");
			exit (error);
			}
		/* Set name and priority, hang clock's data structure on task
		   structure for access by other processes. */
		timerproc = FindTask (0);
		oldname = timerproc->pr_Task.tc_Node.ln_Name;
		timerproc->pr_Task.tc_Node.ln_Name = MIDICLOCKNAME;
		timerproc->pr_Task.tc_UserData = (APTR) &time;
		oldpri = SetTaskPri (timerproc, MC_PRIORITY);
		myport = &(timerproc->pr_MsgPort);
		printf ("\nMIDICLOCK: started, rate is %d ticks/sec.\n", ticks);

		for (quit = 0; !quit; ) {
			WaitPort (myport); /* wait for control messages */
			while (msg = GetMsg (myport)) {
				switch (msg->command) {
					case KILL:
						quit = 1;
						printf ("MIDICLOCK: terminated.\n");
						break;
					case RATE:
						StopCIATimer ();
						SetCIATimer (msg->data);
						StartCIATimer ();
						printf ("MIDICLOCK: rate is %d ticks/sec.\n", msg->data);
						break;
					default:
					}
				ReplyMsg (msg);
				}
			}
		EndCIATimer (); /* stop timer and clear interrupt */
		/* In the event that this process is attached to a CLI,
		   restore its name and priority */
		timerproc->pr_Task.tc_Node.ln_Name = oldname;
		SetTaskPri (timerproc, oldpri);
		exit (0);
		}

	/* Timer process is already running, so this must be a control
	   job.
	*/

	if ((rpyport = CreatePort (0, 0)) == NULL) {
		printf ("MIDICLOCK: Can't create reply port.\n");
		goto cleanup;
		}
	ctlmsg.msg.mn_Node.ln_Type = NT_MESSAGE;
	ctlmsg.msg.mn_ReplyPort    = rpyport;

	timerport = &(timerproc->pr_MsgPort);
	/* process command line args, send control messages to timer */
	for (i = 1; i < argc; i++) {
		pt = argv[i];
		if (*pt == '-') {
			switch (*++pt) {
				case 'q': /* terminate */
					ctlmsg.command = KILL;
					break;
				case 't': /* change rate */
					if (++i < argc) {
						ctlmsg.command = RATE;
						ctlmsg.data = atoi (argv[i]);
						}
					else ctlmsg.command = NOMSG;
					break;
				default:
				}
			}
		else { /* non-switch argument is assumed to be time value */
			ctlmsg.command = RATE;
			ctlmsg.data = atoi (argv[i]);
			}
		if (ctlmsg.command != NOMSG) { /* send message, await reply */
			PutMsg (timerport, &ctlmsg);
			WaitPort (rpyport);
			GetMsg (rpyport);
			}
		}

cleanup:
	if (rpyport) DeletePort (rpyport);
	return 0;
}
