/*****************************************************************************/
/*                                                                           */
/* Module:  POKEY Chip Simulator, V1.2                                       */
/* Purpose: To emulate the sound generation hardware of the Atari POKEY chip.*/
/* Author:  Ron Fries                                                        */
/*          Thomas Richter                                                   */
/* Date:    September 22, 1996                                               */
/*          February 17,1998                                                 */
/*                                                                           */
/*****************************************************************************/
/*                                                                           */
/*                 License Information and Copyright Notice                  */
/*                 ========================================                  */
/*                                                                           */
/* PokeySound is Copyright(c) 1996 by Ron Fries                              */
/*                                                                           */
/* This library is free software; you can redistribute it and/or modify it   */
/* under the terms of version 2 of the GNU Library General Public License    */
/* as published by the Free Software Foundation.                             */
/*                                                                           */
/* This library 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 Library */
/* General Public License for more details.                                  */
/* To obtain a copy of the GNU Library General Public License, write to the  */
/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.   */
/*                                                                           */
/* Any permitted reproduction of these routines, in whole or in part, must   */
/* bear this legend.                                                         */
/*                                                                           */
/*****************************************************************************/

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

#include "pokey.h"
#include "pokey11.h"
#include "sio.h"
#include "mem.h"
#include "cpu.h"
#include "platform.h"

/* CONSTANT DEFINITIONS */

/* definitions for AUDCx (D201, D203, D205, D207) */
#define NOTPOLY5    0x80     /* selects POLY5 or direct CLOCK */
#define POLY4       0x40     /* selects POLY4 or POLY17 */
#define PURE        0x20     /* selects POLY4/17 or PURE tone */
#define VOL_ONLY    0x10     /* selects VOLUME OUTPUT ONLY */
#define VOLUME_MASK 0x0f     /* volume mask */

/* definitions for AUDCTL (D208) */
#define POLY9       0x80     /* selects POLY9 or POLY17 */
#define CH1_179     0x40     /* selects 1.78979 MHz for Ch 1 */
#define CH3_179     0x20     /* selects 1.78979 MHz for Ch 3 */
#define CH1_CH2     0x10     /* clocks channel 1 w/channel 2 */
#define CH3_CH4     0x08     /* clocks channel 3 w/channel 4 */
#define CH1_FILTER  0x04     /* selects channel 1 high pass filter */
#define CH2_FILTER  0x02     /* selects channel 2 high pass filter */
#define CLOCK_15    0x01     /* selects 15.6999kHz or 63.9210kHz */


/* for accuracy, the 64kHz and 15kHz clocks are exact divisions of
   the 1.79MHz clock */
#define DIV_64      28       /* divisor for 1.79MHz clock to 64 kHz */
#define DIV_15      114      /* divisor for 1.79MHz clock to 15 kHz */

/* the size (in entries) of the 4 polynomial tables */
#define POLY4_SIZE  0x000f
#define POLY5_SIZE  0x001f
#define POLY9_SIZE  0x01ff

#ifdef COMP16						   /* if 16-bit compiler */
#define POLY17_SIZE 0x00007fffL    /* reduced to 15 bits for simplicity */
#else
#define POLY17_SIZE 0x0001ffffL    /* else use the full 17 bits */
#endif


#define FALSE       0
#define TRUE        1


/* GLOBAL VARIABLE DEFINITIONS */
 
/* structures to hold the 9 pokey control bytes */ 
UBYTE AUDF[4];    /* AUDFx (D200, D202, D204, D206) */
UBYTE AUDC[4];    /* AUDCx (D201, D203, D205, D207) */
UBYTE AUDCTL;     /* AUDCTL (D208) */      

static UBYTE Outbit[4];  /* current state of the output (high or low) */

static UBYTE Outvol[4];  /* last output volume for each channel */

int DELAYED_SERIN_IRQ;
int DELAYED_SEROUT_IRQ;
int DELAYED_XMTDONE_IRQ;

/* Initialze the bit patterns for the polynomials. */

/* The 4bit and 5bit patterns are the identical ones used in the pokey chip. */
/* Though the patterns could be packed with 8 bits per byte, using only a */
/* single bit per byte keeps the math simple, which is important for */
/* efficient processing. */

static UBYTE bit4[POLY4_SIZE] = 
      { 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 };

static UBYTE bit5[POLY5_SIZE] = 
      { 0,0,1,1,0,0,0,1,1,1,1,0,0,1,0,1,0,1,1,0,1,1,1,0,1,0,0,0,0,0,1 };

static UBYTE bit17[POLY17_SIZE];  /* Rather than have a table with 131071 */
                            /* entries, I use a random number generator. */
                            /* It shouldn't make much difference since */
                            /* the pattern rarely repeats anyway. */

static ULONG Poly17_size; /* global for the poly 17 size, since it can */
                           /* be changed from 17 bit to 9 bit */

static ULONG Poly_adjust; /* the amount that the polynomial will need */
                           /* to be adjusted to process the next bit */

static ULONG  P4=0,   /* Global position pointer for the 4-bit  POLY array */
              P5=0,   /* Global position pointer for the 5-bit  POLY array */
              P17=0;  /* Global position pointer for the 17-bit POLY array */

ULONG         Div_n_cnt[4],   /* Divide by n counter. one for each channel */
              Div_n_max[4],   /* Divide by n maximum, one for each channel */
              Div_n_irq[4];   /* Just the same counter, but used for IRQ */

static ULONG Samp_n_max,     /* Sample max.  For accuracy, it is *256 */
             Samp_n_cnt[2];  /* Sample cnt. */

static ULONG Base_mult;      /* selects either 64Khz or 15Khz clock mult */

UBYTE KBCODE;
UBYTE IRQST;
UBYTE IRQEN;
UBYTE SKSTAT;

static char *rcsid = "$Id: pokey11.c,v 1.2 1998/02/17 Ron Fries,thor";

extern UWORD regPC;

void Update_Sound(int chan_mask);
       
/*****************************************************************************/
/* In my routines, I treat the sample output as another divide by N counter  */
/* For better accuracy, the Samp_n_cnt has a fixed binary decimal point      */
/* which has 8 binary digits to the right of the decimal point.  I use a two */
/* byte array to give me a minimum of 40 bits, and then use pointer math to  */
/* reference either the 24.8 whole/fraction combination or the 32-bit whole  */
/* only number.  This is mainly used to keep the math simple for             */
/* optimization. See below:                                                  */
/*                                                                           */
/* xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx | xxxxxxxx xxxxxxxx xxxxxxxx.xxxxxxxx */
/*  unused   unused   unused    whole      whole    whole    whole  fraction */
/*                                                                           */
/* Samp_n_cnt[0] gives me a 32-bit int 24 whole bits with 8 fractional bits, */
/* while (ULONG *)((UBYTE *)(&Samp_n_cnt[0])+1) gives me the 32-bit whole   */
/* number only.                                                              */
/*****************************************************************************/

/* Register GET routines */

mtype POKEY_KBCODE_GET(void)
{
  return KBCODE;
}

mtype POKEY_IRQST_GET(void)
{
  return IRQST;
}

mtype POKEY_POT0_GET(void)
{
  return Atari_POT(0);
}

mtype POKEY_POT1_GET(void)
{
  return Atari_POT(1);
}

mtype POKEY_POT2_GET(void)
{
  return Atari_POT(2);
}

mtype POKEY_POT3_GET(void)
{
  return Atari_POT(3);
}

mtype POKEY_POT4_GET(void)
{
  return Atari_POT(4);
}

mtype POKEY_POT5_GET(void)
{
  return Atari_POT(5);
}

mtype POKEY_POT6_GET(void)
{
  return Atari_POT(6);
}

mtype POKEY_POT7_GET(void)
{
  return Atari_POT(7);
}

mtype POKEY_RANDOM_GET(void)
{
static int rand_init = 0;

  if (!rand_init) {
    srand((int) time((time_t *) NULL));
    rand_init = 1;
  }
  return rand();
}

mtype POKEY_SERIN_GET(void)
{
  return SIO_GetByte();
}

mtype POKEY_SKSTAT_GET(void)
{
#ifdef BASIC
  return 0xff;
#else
  return Atari_Keyboard_State();
#endif
}


/* IO Put routines */

int POKEY_AUDF1_PUT(mtype val)
{
  AUDF[CHAN1] = val;
  Update_Sound((AUDCTL & CH1_CH2)?((1<<CHAN2)|(1<<CHAN1)):(1<<CHAN1));
  return FALSE;
}

int POKEY_AUDC1_PUT(mtype val)
{
  AUDC[CHAN1] = val;
  Update_Sound(1<<CHAN1);
  return FALSE;
}

int POKEY_AUDF2_PUT(mtype val)
{
  AUDF[CHAN2] = val;
  Update_Sound(1<<CHAN2);
  return FALSE;
}

int POKEY_AUDC2_PUT(mtype val)
{
  AUDC[CHAN2] = val;
  Update_Sound(1<<CHAN2);
  return FALSE;
}

int POKEY_AUDF3_PUT(mtype val)
{
  AUDF[CHAN3] = val;
  Update_Sound((AUDCTL & CH3_CH4)?((1<<CHAN4)|(1<<CHAN3)):(1<<CHAN3));
  return FALSE;
}

int POKEY_AUDC3_PUT(mtype val)
{
  AUDC[CHAN3] = val;
  Update_Sound(1<<CHAN3);
  return FALSE;
}

int POKEY_AUDF4_PUT(mtype val)
{
  AUDF[CHAN4] = val;
  Update_Sound(1<<CHAN4);
  return FALSE;
}

int POKEY_AUDC4_PUT(mtype val)
{
  AUDC[CHAN4] = val;
  Update_Sound(1<<CHAN4);
  return FALSE;
}

int POKEY_AUDCTL_PUT(mtype val)
{
  AUDCTL = val;

  if (AUDCTL & POLY9)
    Poly17_size = POLY9_SIZE;
  else
    Poly17_size = POLY17_SIZE;
	  
  /* determine the base multiplier for the 'div by n' calculations */
  if (AUDCTL & CLOCK_15)
    Base_mult = DIV_15;
  else
    Base_mult = DIV_64;
  
  Update_Sound((1<<CHAN1)|(1<<CHAN2)|(1<<CHAN3)|(1<<CHAN4));
  return FALSE;
}

int POKEY_IRQEN_PUT(mtype val)
{
  IRQEN = val;
  IRQST |= ~val;
  return FALSE;
}

int POKEY_SEROUT_PUT(mtype val)
{
  if ((SKSTAT & 0x70) == 0x20) {
    if ((AUDF[CHAN3] == 0x28) && (AUDF[CHAN4] == 0x00) && (AUDCTL & 0x28)==0x28)
      SIO_PutByte(val);
  }
  return FALSE;
}

int POKEY_STIMER_PUT(mtype val)
{
  Div_n_cnt[CHAN1]=Div_n_irq[CHAN1]=0;
  Div_n_cnt[CHAN2]=Div_n_irq[CHAN2]=0;
  Div_n_cnt[CHAN3]=Div_n_irq[CHAN3]=0;
  Div_n_cnt[CHAN4]=Div_n_irq[CHAN4]=0;
  return FALSE;
}

int POKEY_SKSTAT_PUT(mtype val)
{
  SKSTAT = val;
  return FALSE;
}


/*****************************************************************************/
/* Module:  Pokey_sound_init()                                               */
/* Purpose: to handle the power-up initialization functions                  */
/*          these functions should only be executed on a cold-restart        */
/*                                                                           */
/* Author:  Ron Fries                                                        */
/* Date:    September 22, 1996                                               */
/*                                                                           */
/* Inputs:  freq17 - the value for the '1.79MHz' Pokey audio clock           */
/*          playback_freq - the playback frequency in samples per second     */
/*                                                                           */
/* Outputs: Adjusts local globals - no return value                          */
/*                                                                           */
/*****************************************************************************/

void Init_Pokey(int *argc,char **argv,int base)
{
int chan;
long n;

   /* fill the 17bit polynomial with random bits */
   for (n=0; n<POLY17_SIZE; n++)
   {
      bit17[n] = rand() & 0x01;       /* fill poly 17 with random bits */
   }

   /* start all of the polynomial counters at zero */
   Poly_adjust = 0;
   P4 = 0;
   P5 = 0;
   P17 = 0;

   Samp_n_cnt[0] = 0;  /* initialize all bits of the sample */
   Samp_n_cnt[1] = 0;  /* 'divide by N' counter */

   Poly17_size = POLY17_SIZE;
   for (chan = CHAN1; chan <= CHAN4; chan++)
     {
       Outvol[chan] = 0;
       Outbit[chan] = 0;
       Div_n_cnt[chan] = 0;
       Div_n_irq[chan] = 0;
       Div_n_max[chan] = 0x7fffffffL;
       AUDC[chan] = 0;
       AUDF[chan] = 0;
     }
  
   AUDCTL = 0;
   IRQST = 0xff;
   IRQEN = 0x00;
   Base_mult = DIV_64;  
   /*
    * Initialise Serial Port Interrupts
    */
  
   DELAYED_SERIN_IRQ = 0;
   DELAYED_SEROUT_IRQ = 0;
   DELAYED_XMTDONE_IRQ = 0;

   if (argc)
     Samp_n_max = 1;   /* provide some meaningfull default if hard reset */

   SetHW(base+_AUDF1,0xff0f,&POKEY_POT0_GET,&POKEY_AUDF1_PUT);
   SetHW(base+_AUDC1,0xff0f,&POKEY_POT1_GET,&POKEY_AUDC1_PUT);
   SetHW(base+_AUDF2,0xff0f,&POKEY_POT2_GET,&POKEY_AUDF2_PUT);
   SetHW(base+_AUDC2,0xff0f,&POKEY_POT3_GET,&POKEY_AUDC2_PUT);
   SetHW(base+_AUDF3,0xff0f,&POKEY_POT4_GET,&POKEY_AUDF3_PUT);
   SetHW(base+_AUDC3,0xff0f,&POKEY_POT5_GET,&POKEY_AUDC3_PUT);
   SetHW(base+_AUDF4,0xff0f,&POKEY_POT6_GET,&POKEY_AUDF4_PUT);
   SetHW(base+_AUDC4,0xff0f,&POKEY_POT7_GET,&POKEY_AUDC4_PUT);
   SetHW(base+_AUDCTL,0xff0f,NULL,&POKEY_AUDCTL_PUT);
   SetHW(base+_STIMER,0xff0f,&POKEY_KBCODE_GET,&POKEY_STIMER_PUT);
   SetHW(base+_SKRES,0xff0f,&POKEY_RANDOM_GET,NULL);
   SetHW(base+_POTGO,0xff0f,NULL,NULL);
   SetHW(base+_SEROUT,0xff0f,&POKEY_SERIN_GET,&POKEY_SEROUT_PUT);
   SetHW(base+_IRQEN,0xff0f,&POKEY_IRQST_GET,&POKEY_IRQEN_PUT);
   SetHW(base+_SKCTLS,0xff0f,&POKEY_SKSTAT_GET,&POKEY_SKSTAT_PUT);
}

void Pokey_sound_init (ULONG freq17, UWORD playback_freq)
{
   Samp_n_max = ((ULONG)freq17 << 8) / playback_freq;
}


/*****************************************************************************/
/* Module:  Update_Sound()                                                   */
/* Purpose: To process the latest control values stored in the AUDF, AUDC,   */
/*          and AUDCTL registers.  It pre-calculates as much information as  */
/*          possible for better performance.  This routine has not been      */
/*          optimized.                                                       */
/*                                                                           */
/* Author:  Ron Fries                                                        */
/* Date:    September 22, 1996                                               */
/*                                                                           */
/* Inputs:  addr - the address of the parameter to be changed                */
/*          val - the new value to be placed in the specified address        */
/*                                                                           */
/* Outputs: Adjusts local globals - no return value                          */
/*                                                                           */
/*****************************************************************************/

void Update_Sound(int chan_mask)
{
ULONG new_val = 0;
int chan;

    /************************************************************/
    /* As defined in the manual, the exact Div_n_cnt values are */
    /* different depending on the frequency and resolution:     */
    /*    64 kHz or 15 kHz - AUDF + 1                           */
    /*    1 MHz, 8-bit -     AUDF + 4                           */
    /*    1 MHz, 16-bit -    AUDF[CHAN1]+256*AUDF[CHAN2] + 7    */
    /************************************************************/

    /* only reset the channels that have changed */

    if (chan_mask & (1 << CHAN1))
    {
       /* process channel 1 frequency */
       if (AUDCTL & CH1_179)
          new_val = AUDF[CHAN1] + 4;
       else
          new_val = (AUDF[CHAN1] + 1) * Base_mult;

       if (new_val != Div_n_max[CHAN1])
       {
          Div_n_max[CHAN1] = new_val;
          Div_n_cnt[CHAN1] = 0;
	  Div_n_irq[CHAN1] = 0;
       }
    }

    if (chan_mask & (1 << CHAN2))
    {
       /* process channel 2 frequency */
       if (AUDCTL & CH1_CH2)
          if (AUDCTL & CH1_179)
             new_val = AUDF[CHAN2] * 256 + AUDF[CHAN1] + 7;
          else
             new_val = (AUDF[CHAN2] * 256 + AUDF[CHAN1] + 1) * Base_mult;
       else
          new_val = (AUDF[CHAN2] + 1) * Base_mult;

       if (new_val != Div_n_max[CHAN2])
       {
          Div_n_max[CHAN2] = new_val;
          Div_n_cnt[CHAN2] = 0;
	  Div_n_irq[CHAN2] = 0;
       }
    }

    if (chan_mask & (1 << CHAN3))
    {
       /* process channel 3 frequency */
       if (AUDCTL & CH3_179)
          new_val = AUDF[CHAN3] + 4;
       else
          new_val= (AUDF[CHAN3] + 1) * Base_mult;

       if (new_val!= Div_n_max[CHAN3])
       {
          Div_n_max[CHAN3] = new_val;
          Div_n_cnt[CHAN3] = 0;
	  Div_n_irq[CHAN3] = 0;
       }
    }

    if (chan_mask & (1 << CHAN4))
    {
       /* process channel 4 frequency */
       if (AUDCTL & CH3_CH4)
          if (AUDCTL & CH3_179)
             new_val = AUDF[CHAN4] * 256 + AUDF[CHAN3] + 7;
          else
             new_val = (AUDF[CHAN4] * 256 + AUDF[CHAN3] + 1) * Base_mult;
       else
          new_val = (AUDF[CHAN4] + 1) * Base_mult;

       if (new_val != Div_n_max[CHAN4])
       {
          Div_n_max[CHAN4] = new_val;
          Div_n_cnt[CHAN4] = 0;
	  Div_n_irq[CHAN4] = 0;
       }
    }

    /* if channel is volume only, set current output */
    for (chan = CHAN1; chan <= CHAN4; chan++)
    {
       if (chan_mask & (1 << chan))
       {
          /* I've disabled any frequencies that exceed the sampling
             frequency.  There isn't much point in processing frequencies
             that the hardware can't reproduce.  I've also disabled 
             processing if the volume is zero. */

          /* if the channel is volume only */
          /* or the channel is off (volume == 0) */
          /* or the channel freq is greater than the playback freq */
          if ((AUDC[chan] & VOL_ONLY) ||
             ((AUDC[chan] & VOLUME_MASK) == 0) ||
              (Div_n_max[chan] < (Samp_n_max >> 8)))
          {
             /* then set the channel to the selected volume */
             Outvol[chan] = AUDC[chan] & VOLUME_MASK;
             /* and set channel freq to max to reduce processing */
             /* Div_n_max[chan] = 0x7fffffffL;
		Removed. Conflicts with POKEY-IRQs */
          }
       }
    } 
}


/*****************************************************************************/
/* Module:  Pokey_process_2()                                                */
/* Purpose: To fill the output buffer with the sound output based on the     */
/*          pokey chip parameters.  This routine has not been optimized.     */
/*          Though it is not used by the program, I've left it for reference.*/
/*                                                                           */
/* Author:  Ron Fries                                                        */
/* Date:    September 22, 1996                                               */
/*                                                                           */
/* Inputs:  *buffer - pointer to the buffer where the audio output will      */
/*                    be placed                                              */
/*          n - size of the playback buffer                                  */
/*                                                                           */
/* Outputs: the buffer will be filled with n bytes of audio - no return val  */
/*                                                                           */
/*****************************************************************************/

void Pokey_process_2 (register unsigned char *buffer, register UWORD n)
{
#ifdef VOXWARE
    register ULONG *samp_cnt_w_ptr;
    register ULONG event_min;
    register UBYTE next_event;
    register UBYTE cur_val;
    register UBYTE chan;

    /* set a pointer to the whole portion of the samp_n_cnt */
    samp_cnt_w_ptr = (ULONG *)((UBYTE *)(&Samp_n_cnt[0])+1);

    /* loop until the buffer is filled */
    while (n)
    {
       /* Normally the routine would simply decrement the 'div by N' */
       /* counters and react when they reach zero.  Since we normally */
       /* won't be processing except once every 80 or so counts, */
       /* I've optimized by finding the smallest count and then */
       /* 'accelerated' time by adjusting all pointers by that amount. */

       /* find next smallest event (either sample or chan 1-4) */
       next_event = SAMPLE;
       event_min = *samp_cnt_w_ptr;

       for (chan = CHAN1; chan <= CHAN4; chan++)
       {
          if (Div_n_cnt[chan] <= event_min)
          {
             event_min = Div_n_cnt[chan];
             next_event = chan;
          }
       }


       /* decrement all counters by the smallest count found */
       for (chan = CHAN1; chan <= CHAN4; chan++)
       {
          Div_n_cnt[chan] -= event_min;
       }

       *samp_cnt_w_ptr -= event_min;

       /* since the polynomials require a mod (%) function which is 
          division, I don't adjust the polynomials on the SAMPLE events,
          only the CHAN events.  I have to keep track of the change,
          though. */
       Poly_adjust += event_min;

       /* if the next event is a channel change */
       if (next_event != SAMPLE)
       {
          /* shift the polynomial counters */
          P4  = (P4  + Poly_adjust) % POLY4_SIZE;
          P5  = (P5  + Poly_adjust) % POLY5_SIZE;
          P17 = (P17 + Poly_adjust) % Poly17_size;
         
          /* reset the polynomial adjust counter to zero */
          Poly_adjust = 0;

          /* adjust channel counter */
          Div_n_cnt[next_event] += Div_n_max[next_event];

          /* From here, a good understanding of the hardware is required */
          /* to understand what is happening.  I won't be able to provide */
          /* much description to explain it here. */

          /* if the output is pure or the output is poly5 and the poly5 bit */
          /* is set */
          if ((AUDC[next_event] & NOTPOLY5) || bit5[P5])
          {
             /* if the PURE bit is set */
             if (AUDC[next_event] & PURE)
             {
                /* then simply toggle the output */
                Outbit[next_event] = !Outbit[next_event];
             }
             /* otherwise if POLY4 is selected */
             else if (AUDC[next_event] & POLY4)
             {
                /* then use the poly4 bit */
                Outbit[next_event] = bit4[P4];
             }
             else
             {
                /* otherwise use the poly17 bit */
                Outbit[next_event] = bit17[P17];
             }
          }

          /* At this point I haven't emulated the filters.  Though I don't
             expect it to be complicated, I don't believe this feature is
             used much anyway.  I'll work on it later. */
          if ((next_event == CHAN1) || (next_event == CHAN3))
          {
             /* INSERT FILTER HERE */
          }

          /* if the current output bit is set */
          if (Outbit[next_event])
          {
             /* then set to the current volume */
             Outvol[next_event] = AUDC[next_event] & VOLUME_MASK;
          }
          else
          {
             /* set the volume to zero */
             Outvol[next_event] = 0;
          }
       }
       else /* otherwise we're processing a sample */
       {
          /* adjust the sample counter - note we're using the 24.8 integer
             which includes an 8 bit fraction for accuracy */
          *Samp_n_cnt += Samp_n_max;

          cur_val = 0;

          /* add the output values of all 4 channels */
          for (chan = CHAN1; chan <= CHAN4; chan++)
          {
             cur_val += Outvol[chan];
          }

          /* multiply the volume by 4 and add 8 to center around 128 */
          /* NOTE: this statement could be eliminated for efficiency, */
          /* though the volume would be lower. */
          cur_val = (cur_val << 2) + 8;
          
          /* add the current value to the output buffer */
          *buffer++ = cur_val;
          
          /* and indicate one less byte in the buffer */
          n--;          
       }
    }
#endif
}      

                              
/*****************************************************************************/
/* Module:  Pokey_process()                                                  */
/* Purpose: To fill the output buffer with the sound output based on the     */
/*          pokey chip parameters.  This routine has not been optimized.     */
/*          Though it is not used by the program, I've left it for reference.*/
/*                                                                           */
/* Author:  Ron Fries                                                        */
/* Date:    September 22, 1996                                               */
/*                                                                           */
/* Inputs:  *buffer - pointer to the buffer where the audio output will      */
/*                    be placed                                              */
/*          n - size of the playback buffer                                  */
/*                                                                           */
/* Outputs: the buffer will be filled with n bytes of audio - no return val  */
/*                                                                           */
/*****************************************************************************/

void Pokey_process (register unsigned char *buffer, register UWORD n)
{
#ifdef VOXWARE

    register ULONG *div_n_ptr;
    register ULONG *samp_cnt_w_ptr;
    register ULONG event_min;
    register UBYTE next_event;
    register UBYTE cur_val;
    register UBYTE *out_ptr;
    register UBYTE audc;
    register UBYTE toggle;


    /* set a pointer to the whole portion of the samp_n_cnt */
    samp_cnt_w_ptr = (ULONG *)((UBYTE *)(&Samp_n_cnt[0])+1);
   
    /* set a pointer for optimization */
    out_ptr = Outvol;

    /* The current output is pre-determined and then adjusted based on each */
    /* output change for increased performance (less over-all math). */
    /* add the output values of all 4 channels */
    cur_val  = 2;                 /* start with a small offset */
    cur_val += *out_ptr++;
    cur_val += *out_ptr++;
    cur_val += *out_ptr++;
    cur_val += *out_ptr++;

    /* loop until the buffer is filled */
    while (n)
    {
       /* Normally the routine would simply decrement the 'div by N' */
       /* counters and react when they reach zero.  Since we normally */
       /* won't be processing except once every 80 or so counts, */
       /* I've optimized by finding the smallest count and then */
       /* 'accelerated' time by adjusting all pointers by that amount. */

       /* find next smallest event (either sample or chan 1-4) */
       next_event = SAMPLE;
       event_min = *samp_cnt_w_ptr;

       /* Though I could have used a loop here, this is faster */
       div_n_ptr = Div_n_cnt;
       if (*div_n_ptr <= event_min)
       {
          event_min = *div_n_ptr;
          next_event = CHAN1;
       }
       div_n_ptr++;
       if (*div_n_ptr <= event_min)
       {
          event_min = *div_n_ptr;
          next_event = CHAN2;
       }
       div_n_ptr++;
       if (*div_n_ptr <= event_min)
       {
          event_min = *div_n_ptr;
          next_event = CHAN3;
       }
       div_n_ptr++;
       if (*div_n_ptr <= event_min)
       {
          event_min = *div_n_ptr;
          next_event = CHAN4;
       }

       /* decrement all counters by the smallest count found */
       /* again, no loop for efficiency */
       *div_n_ptr -= event_min;
       div_n_ptr--;
       *div_n_ptr -= event_min;
       div_n_ptr--;
       *div_n_ptr -= event_min;
       div_n_ptr--;
       *div_n_ptr -= event_min;

       *samp_cnt_w_ptr -= event_min;

       /* since the polynomials require a mod (%) function which is 
          division, I don't adjust the polynomials on the SAMPLE events,
          only the CHAN events.  I have to keep track of the change,
          though. */
       Poly_adjust += event_min;

       /* if the next event is a channel change */
       if (next_event != SAMPLE)
       {
          /* shift the polynomial counters */
          P4  = (P4  + Poly_adjust) % POLY4_SIZE;
          P5  = (P5  + Poly_adjust) % POLY5_SIZE;
          P17 = (P17 + Poly_adjust) % Poly17_size;
         
          /* reset the polynomial adjust counter to zero */
          Poly_adjust = 0;

          /* adjust channel counter */
          Div_n_cnt[next_event] += Div_n_max[next_event];

          /* get the current AUDC into a register (for optimization) */
          audc = AUDC[next_event];

          /* set a pointer to the current output (for opt...) */
          out_ptr = &Outvol[next_event];

          /* assume no changes to the output */
          toggle = FALSE;

          /* From here, a good understanding of the hardware is required */
          /* to understand what is happening.  I won't be able to provide */
          /* much description to explain it here. */

          /* if the output is pure or the output is poly5 and the poly5 bit */
          /* is set */
          if ((audc & NOTPOLY5) || bit5[P5])
          {
             /* if the PURE bit is set */
             if (audc & PURE)
             {               
                /* then simply toggle the output */
                toggle = TRUE;
             }
             /* otherwise if POLY4 is selected */
             else if (audc & POLY4)
             {
                /* then compare to the poly4 bit */
                toggle = (bit4[P4] == !(*out_ptr));
             }
             else
             {
                /* otherwise compare to the poly17 bit */
                toggle = (bit17[P17] == !(*out_ptr));
             }
          }

          /* At this point I haven't emulated the filters.  Though I don't
             expect it to be complicated, I don't believe this feature is
             used much anyway.  I'll work on it later. */
          if ((next_event == CHAN1) || (next_event == CHAN3))
          {
             /* INSERT FILTER HERE */
          }

          /* if the current output bit has changed */
          if (toggle)
          {
             if (*out_ptr)
             {
                /* remove this channel from the signal */
                cur_val -= *out_ptr;
                
                /* and turn the output off */
                *out_ptr = 0;
             }
             else
             {
                /* turn the output on */
                *out_ptr = audc & VOLUME_MASK;

                /* and add it to the output signal */
                cur_val += *out_ptr;
             }
          }
       }
       else /* otherwise we're processing a sample */
       {
          /* adjust the sample counter - note we're using the 24.8 integer
             which includes an 8 bit fraction for accuracy */
          *Samp_n_cnt += Samp_n_max;

          /* add the current value to the output buffer */
          *buffer++ = cur_val << 2;
          
          /* and indicate one less byte in the buffer */
          n--;          
       }
    }
#endif
}                                    
      
/***************************************************************************
 ** Generate POKEY Timer IRQs if required                                 **
 ** called on a per-scanline basis, not very precise, but good enough     **
 ** for most applications                                                 **
 ***************************************************************************/

void POKEY_Scanline(void)
{

  if (DELAYED_SERIN_IRQ > 0) {
    if (--DELAYED_SERIN_IRQ == 0 ) {
      IRQST &= 0xdf;
      if (IRQEN & 0x20) 
	GenerateIRQ();
      /* else printf("SerIn missing.\n"); */
    } 
  }

  if (DELAYED_SEROUT_IRQ > 0) {
    if (--DELAYED_SEROUT_IRQ == 0 ) {
      IRQST &= 0xef;
      if (IRQEN & 0x10)
	GenerateIRQ();
      /* else printf("SerOut missing.\n"); */
      DELAYED_XMTDONE_IRQ += XMTDONE_INTERVAL;
    } 
  }

  if (DELAYED_XMTDONE_IRQ > 0) {
    if (--DELAYED_XMTDONE_IRQ == 0) {
      IRQST &= 0xf7;
      if (IRQEN & 0x08)
	GenerateIRQ(); 
      /* else printf("XMTDone missing.\n"); */
    } 
  }

/* one scanline is 15Khz for an ordinary TV */

  if ((Div_n_irq[CHAN1] += DIV_15)>Div_n_max[CHAN1]) {
    Div_n_irq[CHAN1] = 0;      
    if (IRQEN & 0x01) {
      IRQST &= 0xfe;
      GenerateIRQ();
    }
  }

  if ((Div_n_irq[CHAN2] += DIV_15)>Div_n_max[CHAN2]) {
    Div_n_irq[CHAN2] = 0;
    if (IRQEN & 0x02) {
      IRQST &= 0xfd;
      GenerateIRQ();
    }
  }

  if ((Div_n_irq[CHAN3] += DIV_15)>Div_n_max[CHAN3]) {
    Div_n_irq[CHAN3] = 0;    
  }

  if ((Div_n_irq[CHAN4] += DIV_15)>Div_n_max[CHAN4]) {
    Div_n_irq[CHAN4] = 0;
    if (IRQEN & 0x04) {
      IRQST &= 0xfb;
      GenerateIRQ();
    }
  }
  
}

void SendKey(UBYTE key)
{
  KBCODE = key;
  IRQST  &= 0xbf;
  if (IRQEN & 0x40)
    GenerateIRQ();
}

void SendBRK(void)
{
  IRQST &= 0x7f;
  if (IRQEN & 0x80)
    GenerateIRQ();
}







