/* Program Scale2.c -- calls to "atool31" and "PlayNote" in atool31.c */

/*   ==>  RECOMMENDED <== */
/*    Set tab interval to 3 for listing of this code */

/* Programmer:  Dick Taylor               November 1992 */
/*              99 Valley View Rd, Glastonbury CT 06033 USA */

/* Please refer to file scale.doc for usage and background. */

#include <exec/types.h>
#include <intuition/intuition.h>
#include <exec/ports.h>
#include <devices/timer.h>

#define SECONDS tr_time.tv_secs
#define MICROSECONDS tr_time.tv_micro

extern int  PlayNote(), atool31(), StartChans();
extern char Key, Key2, Note[4], TimeV[4], ttext[];
extern int  Octave[4], Tempo;
extern struct			/* Ascend/Descend structure */
{
	char ad[15];	/* For each of 4 channels, we have */
	int  ct[15];	/* 15 pairs of A/D/R and count, 14 are usable */
}	zChan[4];
extern BOOL fin, cycle;
extern struct Gadget stop_gadget;
extern struct PropInfo tempo_info;
extern struct Gadget tempo_gadget;
extern struct IntuiText tp_text;
extern struct IntuiText stext_request;
extern struct IntuiText sok_request;
extern struct Window *w;

int Increm[4],		/* Increment, either 0 or -12; per channel */
	ADRsig[4],		/* Ascend(1)/Descend(-1)/Rest(0) signal; per chan */
	iADR[4],			/* Position in ADR array; per chan */
	zCt[4],			/* ADR Count for current ADR step; per chan */
	ProtoScale[4][8],	/* Prototype Scale notes, per channel */
	RelTime[4],		/* Relative Time, either 1, 2, or 4; per chan */
	Vol[4],			/* Volume; per chan.  50 = play   0 = rest */
	Duration[4],	/* Note Duration; per chan.  500 = .5 sec */
	iScale = 1,		/* In playing scales after the first octave -- */
						/* 1 = play 7 notes   0 = play 8 notes, all chan's */
	jScale;			/* Same usage as iScale */

char aCh;		/* Temp -- char: A, D, R, + */

BOOL Desc[4],	/* Descending flag T or F; per chan */
	SaveDesc[4],	/* Save Descending flag T or F; per chan */
	Play[4],		/* Play flag T or F; per chan */
	PFlag[4],	/* Play Flag: T for A or D, F for R; per chan */
	Repeat=FALSE,	/* Repeat Flag: T if any chan is repeating (+) */
	ChRep[4],		/* Chan Repeat Flag: T if chan repeating (+); per chan */
	rtn;			/* Flag -- Default F; T if user signals to stop Play */

/* This program uses the TimerDevice to time each 1/1 octave, so that */
/* all notes will be synchronized at the start of each 1/1 octave. */

int secs,		/* Total Wait time for 1/1 octave, in microseconds */
/* Specify time in seconds (xx.yyy) to Timer Device in 2 parts -- */
	I_secs,		/* Whole-number seconds -- xx (integer)*/
	M_secs,		/* Fractional seconds -- yyy in microseconds */
	TimingFF = 10000;		/* Timing Fudge Factor -- empirically */
					/* determined value (= .01 sec), subtracted from secs, */
					/* to smooth transition from one octave to the next. */

struct
{
	int Pitch[32];		/* Structure/array for saving one set of notes */
	int Volume[32];	/* before sending them to PlayNote. */
} Chan[4];				/* One set = either 1, 2, or 4 octaves of notes, */		
							/* depending on Relative Time value -- RelTime[] */
int kPV[4];				/* Position counters -- Pitch/Vol, per Chan */




PlayScale()
{
 int c,		/* Channel -- 0, 1, 2, 3 */
	i,j,m,n;		/* Local variables */
 BOOL First = TRUE;

struct Message *Tmsg;			/* TimerDevice message */
ULONG wakebits,IDCMPsig,TIMERsig;
BOOL ErrOpenTimer = TRUE,err;
int val;

struct timerequest *tr;	/* Request Block Pointer */
struct MsgPort  *tp;	/* Port Pointer */
struct MsgPort  *CreatePort();

 tp = (struct MsgPort *) CreatePort(0,0);	/* Create Port for TimerDevice */
 if (tp == 0)
 {
	stext_request.IText = 
			"Module scale2: Can't create port for TimerDevice; exit.";
	AutoRequest(w,&stext_request,NULL,&sok_request,NULL,NULL,640,72);
	goto StopPlay;
 }

 tr = NULL;		/* Create TimerDevice request block */
 tr = (struct timerequest *) CreateExtIO(tp,sizeof(struct timerequest));
 if (tr == 0)
 {
	stext_request.IText = 
			"Module scale2: Can't create request block for TimerDevice; exit.";
	AutoRequest(w,&stext_request,NULL,&sok_request,NULL,NULL,640,72);
	goto StopPlay;
 }

 ErrOpenTimer = OpenDevice(TIMERNAME,UNIT_VBLANK,tr,0);	/* Open Timer Device */
 if (ErrOpenTimer)
 {
	stext_request.IText = 
			"Module scale2: Can't open TimerDevice; exit.";
	AutoRequest(w,&stext_request,NULL,&sok_request,NULL,NULL,640,72);
	goto StopPlay;
 }

 atool31(0);		/* Initialize the AudioTools system */
 StartChans();		/* Start channels -- will play when receive notes */

 IDCMPsig = 1<<w->UserPort->mp_SigBit;	/* IDCMP signal bit */
 TIMERsig = 1<<tp->mp_SigBit;				/* TimerDevice signal bit */
 rtn = FALSE;

 err = Prelims();	/* Convert some input items to AudioTools numeric form: */
					/* Key Note; per chan -- First Note, prototype scale notes  */ 
 if (err) goto StopPlay;

				/* This major loop plays the scales.  */
do				/* do-while loop -- repeats when cycle = TRUE */
{
	Setup();	/* For each chan, initialize variables & flags */

			/* This loop plays one time through all scale spec's */
			/* It continues to loop as long as any non-+ chan's are active */
	while (zCt[0]+zCt[1]+zCt[2]+zCt[3] > 0)
	{
		GetNotes();	/* Calc 1 set of notes for all chan's; save in array. */
						/* 1 set of notes = Either 1, 2, or 4 octaves per chan, */
						/* depending on value of RelTime[] 1, 2, or 4 */

		if (First)
			First = FALSE;
		else
		{
WaitAgain:					/* Wait for IDCMP signal or Timer signal */
			wakebits = Wait(IDCMPsig | TIMERsig);

			if (wakebits & IDCMPsig)
			{
				DoIDCMP();			/* Process IDCMP message(s) */
			}

			if (wakebits & TIMERsig)
				while (Tmsg = (struct Message *) GetMsg(tp))
					/* Receive Timer message, do nothing */ ;
			else goto WaitAgain;

			if (rtn == TRUE)
				goto StopPlay;
		}

												/* Start the Timer Device */
		tr->tr_node.io_Command = TR_ADDREQUEST;
		tr->SECONDS = I_secs;
		tr->MICROSECONDS = M_secs;
		SendIO(tr);			/* Send Timer command to system */

		PlayIt();		/* Send notes from array to PlayNote in AudioTools */

		jScale = iScale;
	}			/* Close while loop (zCt[0]+zCt[1]+...) */
 }			/* Close do-while loop */
 while (cycle);

 Delay(secs/20000 + 1);		/* Wait for last octave to finish */
								/* Delay for length of last Timer interval */

 while (Tmsg = (struct Message *) GetMsg(tp))
	/* Receive last Timer message, do nothing */ ;

StopPlay:

 atool31(1);		/* Close down the sound system */
 if (!ErrOpenTimer) CloseDevice(tr);	/* Close TimerDevice */ 
 if (tr) DeleteExtIO(tr,sizeof(struct timerequest));	/* Delete IO Block */
 if (tp)  DeletePort(tp);	/* Delete Port */
}		/* Close function PlayScale() */


Prelims()		/* Convert some input items to AudioTools numeric form: */
					/* Key Note; per chan -- First Note, prototype scale notes  */ 
{
 int 
	i,j,m,n,		/* Local variables */
	FNInt,		/* First Note Interval, from key note */
	KeyNote,		/* Key Note -- starting note (0-6) for current key  */
	KeyNoteInt;		/* Key Note Interval -- interval to find starting note */
 static  ScaleInt[8] = {0,2,2,1,2,2,2,1};	/* Intervals major scale */
 char tempKey;

			/* Find the Key note */
KeyNote = (Key - 'A' + 5) % 7;	/* Conv Key CDEFGAB to digit 0-6 */
				/* (0 = C, 1 = D, ... , 6 = B) */
KeyNoteInt = 0;
for (j = 0; j <= KeyNote; ++j)
   KeyNoteInt += ScaleInt[j];		/* Convert Key code to Key note interval */

if (Key2 == '#') KeyNoteInt += 1;
else if (Key2 == 'b') KeyNoteInt -= 1;

 iScale = 1;
 for (i = 0; i < 4; ++i)
 {
	ChRep[i] = FALSE;
	RelTime[i] = TimeV[i] - '0';		/* Conv char 1/2/4 to integer */
	if (zChan[i].ad[0] != ' ' && (RelTime[i] == 2 || RelTime[i] == 4)) iScale = 0;
	Duration[i] = Tempo / RelTime[i];	/* Get note duration */

	FNInt = 0;	/* Find interval from Key note to first note */
	tempKey = Key; n = 0;
	if (Note[i] != ' ')
	while (tempKey != Note[i])	/* Step up key to reach Note[] */
	{
		n += 1;
		FNInt += ScaleInt[n];		/* FNInt = note interval from Key note */
		tempKey += 1;			/* Increm temp key */
		if (tempKey > 'G') tempKey = 'A';
		if (n > 7) 
		{
			stext_request.IText = 
				"Module scale2: program error finding 1st note. Exit";
			AutoRequest(w,&stext_request,NULL,&sok_request,NULL,NULL,640,72);
			return(TRUE);	/* Exit signal */
		}
	}
	
	ProtoScale[i][0] = 12*(Octave[i]-1) + KeyNoteInt + FNInt;	/*Get 1st note of scale */

	m = 1 + n;

	for (j = 1; j < 8; ++j)
	{	ProtoScale[i][j] = ProtoScale[i][j-1] + ScaleInt[m];	/* Rest of scale notes */
		m += 1;
		if (m > 7) m = 1;
	}
 }
return(FALSE);	/* OK return */
}		/* Close function Prelims() */



Setup()		/* For each chan, initialize variables & flags */
{				/* needed to play the scales */
 int i;
 char a;

jScale = 0;
for (i = 0; i < 4; ++i)
{
	iADR[i] = 0;		/* position in A/D series */
	zCt[i] = 0;
	aCh = zChan[i].ad[0];
	if (aCh == ' ') Play[i] = FALSE;	/* Chan doesn't play */
  else
  {
	Play[i] = TRUE; 
	PFlag[i] = TRUE;
	if (aCh == 'D')				/* Descend */
	{	Desc[i] = TRUE;
		Increm[i] = -12;
		ADRsig[i] = -1;
	}
	else if (aCh == 'A')			/* Ascend */
			{	Desc[i] = FALSE; 
				Increm[i] = 0; 
				ADRsig[i] = 1;
			}
			else						/* Rest */
			{	ADRsig[i] = 0;
				PFlag[i] = FALSE;
			}

	zCt[i] = zChan[i].ct[0];	/* Count for first A/D */
	SaveDesc[i] = Desc[i];
  }
 }
}		/* Close function Setup() */



GetNotes()		/* Calc 1 set of notes for all chan's; save in array. */
					/* 1 set of notes = Either 1, 2, or 4 octaves per chan, */
					/* depending on value of RelTime[] 1, 2, or 4 */
{
 int c,i,j,m,n,p;		/* local variables */

	secs = (Tempo * (8-jScale)) * 1000;	/* Total Wait time in microsecs */
	secs -= TimingFF;		/* Empirical fudge factor */
									/* Divide Wait time into 2 parts -- */
	I_secs = secs / 1000000;	/* Whole number (integer) seconds */
	M_secs = secs % 1000000;	/* Fractional part in microsecs */

	for (i = 0; i < 4; ++i)
	{
		kPV[i] = 0;				/* Reset channel note position counters */
		if (ADRsig[i] == 0) Vol[i] = 0;	/* Set volume for each channel */
		else Vol[i] = 50;				/* 0 = rest, 50 = play */
	}

	for (j = jScale; j < 8; ++j)	/* Play 8 or 7 scale notes */
									/* jScale value: 0 = 8 note scale, 1 = 7 notes */
	{
		for (c = 0; c < 4; ++c)		/* Four channels */
		{
			if (Play[c])
			{
				n = j * RelTime[c];
				for (i = 0; i < RelTime[c]; ++i)
				{
					m = (n + i) % 8;
					if (Desc[c]) m = 7 - m;
					p = ProtoScale[c][m] + Increm[c];
					Chan[c].Pitch[kPV[c]] = p;	/* Save pitch in struct/array */
					Chan[c].Volume[kPV[c]] = Vol[c];	/* Save volume */
					kPV[c] += 1;
				}
			}
			if (Play[c])
			{
				if (RelTime[c] == 2 && j == 3) CheckAD(c);
				if (RelTime[c] == 4 && j != 7 && j%2 == 1) CheckAD(c);
			}
		}		/* Close channel loop c */
	}			/* Close scale loop j */

	for (i = 0; i < 4; ++i)
		kPV[i] = 0;				/* Reset channel note position counters */
}		/* Close function GetNotes() */



DoIDCMP()	/* Process IDCMP message(s) */
{
	struct IntuiMessage *Imsg;		/* IDCMP message */
	ULONG class;
	struct Gadget *address;
	int i, val;

	while (Imsg = (struct IntuiMessage *) GetMsg(w->UserPort))
	{
		class = Imsg->Class;
		address = Imsg->IAddress;
		ReplyMsg(Imsg);
		switch (class)
		{
			case CLOSEWINDOW:
				fin = TRUE;
				rtn = TRUE;
				break;
			case GADGETUP:
				if (address == &stop_gadget)
				{
					rtn =TRUE;
				}
				else if (address == &tempo_gadget)
				{
					tp_text.FrontPen = 0;
					PrintIText (w->RPort,&tp_text,0,0);	/* Erase old Tempo val */
					Tempo = 500 - (float)tempo_info.HorizPot/MAXPOT * 400;
					val = (Tempo + 5)/ 10;
					for (i = 2; i > 0; --i)
					{
						ttext[i] = val%10 + '0';
						val = val/10;
					}
					tp_text.FrontPen = 1;
					PrintIText (w->RPort,&tp_text,0,0);	/* Write new Tempo val */
					for (i = 0; i < 4; ++i)
						Duration[i] = Tempo/RelTime[i];	/* New Durations */
					secs = (Tempo * (8-jScale)) * 1000;
					secs -= TimingFF;		/* Empirical fudge factor */
					I_secs = secs / 1000000;	/* New Timer values */
					M_secs = secs % 1000000;
				}
				break;
			default:
				break;
		}	/* Close switch */
	}		/* Close while */
}		/* Close function DoIDCMP() */




PlayIt()		/* Send notes from array to PlayNote in AudioTools */
{
	int c,i,j,m;

	/* Send array notes to PlayNote */
	for (j = jScale; j < 8; ++j)
	{
		for (c = 0; c < 4; ++c)
		{
			if (Play[c])
			{
				for (i = 0; i < RelTime[c]; ++i)
				{
					PlayNote(c,Chan[c].Pitch[kPV[c]],
								  Chan[c].Volume[kPV[c]],Duration[c]);
					kPV[c] += 1;
				}
			}
		}
	}

	for (i = 0; i < 4; ++i)
		if (Play[i]) CheckAD(i);

			/* If using Repeat (+), check when to stop playing */
	if (Repeat)
	{	m = 0;
		for (i=0; i<4; ++i)			/* Are any non-repeats */
			if (!ChRep[i]) m += zCt[i]; /* still playing? */
		if (m == 0)
			for (i=0; i<4; ++i)
				zCt[i] = 0;		/* Set all zCt's = 0 stops play */
	}
}		/* Close function PlayIt() */



CheckAD(i)		/* Process the ADR+ entry to prep for next octave */
  int i;			/* i is the channel */
{
	if (zCt[i] > 1)
	{
		zCt[i] -= 1;		/* Decrement ADR+ step count */
		Increm[i] += ADRsig[i] * 12;	/* Bump increment either up or down */
			/* by 12, for next octave --either higher or lower. */
			/* No change for R = rest */
	}
	else
	{
		iADR[i] += 1;		/* Advance ADR+ entry counter */
		aCh = zChan[i].ad[iADR[i]];	/* Retrieve next ADR+ code */
		if (aCh == ' ')
		{
			Play[i] = FALSE;
			zCt[i] = 0; 	/* All done this chan */
		}
		else
		{								/* Take next AD entry */
			if (aCh == '+')						/* '+' = repeat ADR's */
			{	Repeat = TRUE;		/* Restart at beginning of ADR+ series*/
				ChRep[i] = TRUE;
				iADR[i] = 0;
				PFlag[i] = FALSE;
				aCh = zChan[i].ad[0];
			}
			if (aCh == 'R')
			{
				ADRsig[i] = 0;		/* 'R' = rest */
				Vol[i] = 0;			/* Set Volume to 0 */
			}
			else 
			{	if (aCh == 'D')	/* Check asc/desc */
				{	
					Desc[i] = TRUE;	/* Descending */ 
					ADRsig[i] = -1; 
				}
				else
				{	
					Desc[i] = FALSE;	/* Ascending */
					ADRsig[i] = 1;
				}
				Play[i] = TRUE;
				Vol[i] = 50;		/* Set Volume to 50 */
				if (! PFlag[i])
				{
					PFlag[i] = TRUE;
					if (aCh == 'A') Increm[i] = 0;
					else Increm[i] = -12;
				}
				else if (Desc[i] == SaveDesc[i])
						Increm[i] += ADRsig[i]*12;
				SaveDesc[i] = Desc[i];
			}
			zCt[i] = zChan[i].ct[iADR[i]];	/* Get count this AD */
		}
	}
}		/* Close function CheckAD() */
