/*
    Freq.c - frequency counter for AURA-equipped Amigas.
*/

#include <math.h>
#include <dos.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>

#include <exec/execbase.h>
#include <hardware/cia.h>
#include <resources/cia.h>
#include <intuition/intuition.h>

#include <clib/exec_protos.h>
#include <clib/cia_protos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


#include "freq_gui.c"
#include "freq_gui_temp.c"


/* AURA DEFINES */

#define AURA_L 0xA20000
#define AURA_R 0xA20002

/* UWORD *Aura_L, *Aura_R; defined as internal on each effect */

#define AURA_READ_R() ((*Aura_R) ^ 0x8000)	/* AURA returns a left aligned 12 bit offset binary number */
#define AURA_READ_L() ((*Aura_L) ^ 0x8000)	/* invert MSB so offset binary turns to 2's complement */
#define AURA_WRITE_R(x) (*Aura_R)=(x)		/* AURA needs a 2's complement number to be written to */
#define AURA_WRITE_L(x) (*Aura_L)=(x)		/* an internal inverter on MSB converts to offset binary */

/* END OF AURA DEFINES */



#define STOPA_AND  CIACRAF_TODIN |CIACRAF_PBON | CIACRAF_OUTMODE | CIACRAF_SPMODE

        /*
        ;
        ; AND mask for use with control register A
        ; (interval timer A on either CIA)
        ;
        ; STOP -
        ;       START bit 0 == 0 (STOP IMMEDIATELY)
        ;       PBON  bit 1 == same
        ;       OUT   bit 2 == same
        ;       RUN   bit 3 == 0 (SET CONTINUOUS MODE)
        ;       LOAD  bit 4 == 0 (NO FORCE LOAD)
        ;       IN    bit 5 == 0 (COUNTS 02 PULSES)
        ;       SP    bit 6 == same
        ;       TODIN bit 7 == same (unused on ciacra)

        */

#define STOPB_AND  CIACRBF_ALARM | CIACRBF_PBON | CIACRBF_OUTMODE

        /*
        ;
        ; AND mask for use with control register B
        ; (interval timer B on either CIA)
        ;
        ; STOP -
        ;       START bit 0 == 0 (STOP IMMEDIATELY)
        ;       PBON  bit 1 == same
        ;       OUT   bit 2 == same
        ;       RUN   bit 3 == 0 (SET CONTINUOUS MODE)
        ;       LOAD  bit 4 == 0 (NO FORCE LOAD)
        ;       IN0   bit 5 == 0 (COUNTS 02 PULSES)
        ;       IN1   bit 6 == 0 (COUNTS 02 PULSES)
        ;       ALARM bit 7 == same (TOD alarm control bit)

        */


#define STARTA_OR  CIACRAF_START

        /*
        ;
        ; OR mask for use with control register A
        ; (interval timer A on either CIA)
        ;
        ; START -
        ;
        ;       START bit 0 == 1 (START TIMER)
        ;
        ;       All other bits unaffected.
        ;

        */


#define STARTB_OR  CIACRBF_START

        /*
        ;
        ; OR mask for use with control register B
        ; (interval timer A on either CIA)
        ;
        ; START -
        ;
        ;       START bit 0 == 1 (START TIMER)
        ;
        ;       All other bits unaffected.
        ;

        */


/*
 * Structure which will be used to hold all relevant information about
 * the cia timer we manage to allocate.
 *
 */

struct freetimer
{
    struct Library *ciabase;        /* CIA Library Base             */
    ULONG  timerbit;                /* timer bit allocated          */
    struct CIA *cia;                /* ptr to hardware              */
    UBYTE *ciacr;                   /* ptr to control register      */
    UBYTE *cialo;                   /* ptr to low byte of timer     */
    UBYTE *ciahi;                   /* ptr to high byte of timer    */
    struct Interrupt timerint;      /* Interrupt structure          */
    UBYTE  stopmask;                /* Stop/set-up timer            */
    UBYTE  startmask;               /* Start timer                  */
};

/*
 * Structure which will be used by the interrupt routine called
 * when our cia interval timer generates an interrupt.
 *
 */

struct freqdata
{
    struct Task *task;      /* task to signal */
    ULONG   signal;         /* Signal bit to use */
    ULONG   counter;
    UWORD   *buffer;
    BOOL    signalled;
};




#ifndef TRUE
#define TRUE        1
#define FALSE       0
#endif


/* protos */

    
void Measure(WORD *);
void    StartTimer      (struct freetimer *ft, struct freqdata *ed);
int     FindFreeTimer   (struct freetimer *ft, int preferA);
int     TryTimer        (struct freetimer *ft);


#define SAMPLERATE 10000

/* globals */
unsigned int SampleRate=SAMPLERATE;	/* sample rate */
UWORD TimerRate;		/* number to divide E clock for in order to get sample rate */
UWORD SpS;			/* REAL sample rate in samples per second, can be lower or higher */

int quit=0;

struct CIA *ciaa= (struct CIA *)0xbfe001;
struct CIA *ciab = (struct CIA *)0xbfd000;

extern struct ExecBase *SysBase;




void __asm FreqInterrupt(register __a1 struct freqdata *ed)
{

register UWORD *Aura_L= (UWORD *) AURA_L;


	if (ed->counter){
		ed->counter--;			/* decrement counter */
	(*(ed->buffer++))=AURA_READ_L();	/* store AURA input */
	}
	else {
		if((!ed->signalled)) {
			ed->signalled=TRUE;
			Signal(ed->task,(1L << ed->signal));	/* signal and wait */
		}
	}
}








/*
                                main
*/

main()
{


struct freetimer ft;
struct freqdata ed;

UWORD *buffer;                    


    TimerRate = (UWORD)( (SysBase->ex_EClockFrequency)/SampleRate);	/* get timer constant */
    SpS= (UWORD)( (SysBase->ex_EClockFrequency) / TimerRate);		/* get REAL sample rate */

    buffer=(UWORD *)halloc((SpS+1)*sizeof(UWORD));	/* we really need SpS words */

    ed.buffer = buffer;

    ed.counter= SpS;		/* Count-down SpS times */

    ed.signalled=FALSE;

    if(!buffer)	
    	exit(1);
    	
    if(SetupScreen()) 
    	exit(1);
    if(OpenFreqWindow()) {
	CloseDownScreen();
	exit(1);
    }    


    /* Set up data which will be passed to interrupt */

    ed.task = FindTask(0L);

    if (ed.signal = AllocSignal(-1L)){

	    /* Prepare freetimer structure : set-up interrupt */

	    ft.timerint.is_Node.ln_Type = NT_INTERRUPT;
	    ft.timerint.is_Node.ln_Pri  = 0;
	    ft.timerint.is_Node.ln_Name = "AURA_freqmeter";

	    ft.timerint.is_Data         = (APTR)&ed;
	    ft.timerint.is_Code         = (APTR)FreqInterrupt;


	    /* Call function to find a free CIA interval timer
	     * with flag indicating that we prefer a CIA-A timer.
	     */
	    if (FindFreeTimer(&ft,TRUE)) {

	        /* We found a free interval timer.  Let's start it running. */

	        StartTimer(&ft,&ed);

		while(!quit) {
	    
		    Wait(1L << FreqWnd->UserPort->mp_SigBit | 1L<<ed.signal);

	    	    if(1L << FreqWnd->UserPort->mp_SigBit)
				HandleFreqIDCMP();

		    if(1L<<ed.signal) {
				Measure((WORD *)buffer);
				ed.buffer = buffer;		/* reload buffer pointer */
				ed.counter= SpS;		/* reload sample counter, this restarts the process */
				ed.signalled=FALSE;
		    }
		}

        
		/* Release the interval timer */

		RemICRVector(ft.ciabase,ft.timerbit,&ft.timerint);



	    }

	    FreeSignal(ed.signal);
    }

        
  CloseFreqWindow();
  CloseDownScreen();
}


void Measure(WORD *buffer)		/* ported from an ADSP2181 zero-crossing counter... */
{

#define sgn(i)	(((i>0)? 1:0))


int  AF=0, AX0, AR, i, AY0 = 128;                       /* zero cross noise threshold */

	AX0 = *(buffer++);
	AR  = sgn(AX0);            
		
		
		for(i=0;i<SpS;i++) {

		   if(AR) 			/* if positive, sgn is 1 for positive, 0 for negative */
			goto last_was_pos;
last_was_neg:      AR  = AX0 - AY0;
		   AX0 = *(buffer++);
		   if (AR>=0) 
			AF = AF+1;
		   goto calc_zcr;
last_was_pos:      AR  = AX0 + AY0;
		   AX0 = *(buffer++);
		   if (AR<0)			
			AF = AF+1;
calc_zcr:          AR  = sgn(AR);
		}
	
       	GT_SetGadgetAttrs(FreqGadgets[GD_G],FreqWnd,NULL,GTNM_Number, AF/2,TAG_END);

}


/*
 * This routine sets up the interval timer we allocated with
 * AddICRVector().  Note that we may have already received one, or
 * more interrupts from our timer.  Make no assumptions about the
 * initial state of any of the hardware registers we will be using.
 *
 */

void StartTimer(struct freetimer *ft, struct freqdata *ed)
{
register struct CIA *cia;

cia = ft->cia;

/* Note that there are differences between control register A,
 * and B on each CIA (e.g., the TOD alarm bit, and INMODE bits.
 */

if (ft->timerbit == CIAICRB_TA)
    {
    ft->ciacr = &cia->ciacra;       /* control register A   */
    ft->cialo = &cia->ciatalo;      /* low byte counter     */
    ft->ciahi = &cia->ciatahi;      /* high byte counter    */

    ft->stopmask = STOPA_AND;       /* set-up mask values   */
    ft->startmask = STARTA_OR;
    }
else
    {
    ft->ciacr = &cia->ciacrb;       /* control register B   */
    ft->cialo = &cia->ciatblo;      /* low byte counter     */
    ft->ciahi = &cia->ciatbhi;      /* high byte counter    */

    ft->stopmask = STOPB_AND;       /* set-up mask values   */
    ft->startmask = STARTB_OR;
    }


/* Modify control register within Disable().  This is done to avoid
 * race conditions since our compiler may generate code such as:
 *
 *      value = Read hardware byte
 *      AND  value with MASK
 *      Write value to hardware byte
 *
 * If we take a task switch in the middle of this sequence, two tasks
 * trying to modify the same register could trash each others' bits.
 *
 * Normally this code would be written in assembly language using atomic
 * instructions so that the Disable() would not be needed.
 */


Disable();

/* STOP timer, set 02 pulse count-down mode, set continuous mode */

*ft->ciacr &= ft->stopmask;
Enable();

/* Clear signal bit - interrupt will signal us later */
SetSignal(0L,1L<<ed->signal);


/* Start the interval timer - we will start the counter after
 * writing the low, and high byte counter values
 */

*ft->cialo = (UBYTE)TimerRate&0xff;
*ft->ciahi = (UBYTE)TimerRate>>8;

/* Turn on start bit - same bit for both A, and B control regs  */

Disable();
*ft->ciacr |= ft->startmask;

Enable();
}



/*
 * A routine to find a free interval timer.
 *
 * This routine makes no assumptions about which interval timers
 * (if any) are available for use.  Currently there are two interval
 * timers per CIA chip.
 *
 * Because CIA usage may change in the future, your code should use
 * a routine like this to find a free interval timer.
 *
 * Note that the routine takes a preference flag (which is used to
 * to indicate that you would prefer an interval timer on CIA-A).
 * If the flag is FALSE, it means that you would prefer an interval
 * timer on CIA-B.
 *
 */

FindFreeTimer(struct freetimer *ft, int preferA)
{
struct CIABase *ciaabase, *ciabbase;

/* get pointers to both resource bases */

ciaabase = OpenResource(CIAANAME);
ciabbase = OpenResource(CIABNAME);

/* try for a CIA-A timer first ? */

if (preferA)
    {
    ft->ciabase = (struct Library *) ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = (struct Library *) ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

/* try for an interval timer on the other cia */

if (!(preferA))
    {
    ft->ciabase = (struct Library *) ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = (struct Library *) ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

return(FALSE);

}


/*
 * Try to obtain a free interval timer on a CIA.
 */

TryTimer(struct freetimer *ft)
{

if (!(AddICRVector(ft->ciabase,CIAICRB_TA,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TA;
    return(TRUE);
    }

if (!(AddICRVector(ft->ciabase,CIAICRB_TB,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TB;
    return(TRUE);
    }

return(FALSE);
}
