/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program 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 program 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.
**
**
** nes_apu.c
**
** NES APU emulation
** $Id$
*/

#include <string.h>
#include "types.h"
#include "log.h"
#include "nes_apu.h"
#include "nes6502.h"

#ifdef NSF_PLAYER
#include "nsf.h"
#else
#include "nes.h"
#include "nes_ppu.h"
#include "nes_mmc.h"
#include "nesinput.h"
#endif /* !NSF_PLAYER */


#define  APU_OVERSAMPLE
#define  APU_VOLUME_DECAY(x)  ((x) -= ((x) >> 7))


/* pointer to active APU */
static apu_t *apu;

/* look up table madness */
static int32 decay_lut[16];
static int vbl_lut[32];
static int trilength_lut[128];

/* noise lookups for both modes */
#ifndef REALTIME_NOISE
static int8 noise_long_lut[APU_NOISE_32K];
static int8 noise_short_lut[APU_NOISE_93];
#endif /* !REALTIME_NOISE */

/* $$$ ben : last error */
#define SET_APU_ERROR(APU,X) \
if (APU) (APU)->errstr = "apu: " X; else

#define APU_MIX_ENABLE(BIT) (apu->mix_enable&(1<<(BIT)))

/* vblank length table used for rectangles, triangle, noise */
static const uint8 vbl_length[32] = {
  5, 127,
  10, 1,
  19, 2,
  40, 3,
  80, 4,
  30, 5,
  7, 6,
  13, 7,
  6, 8,
  12, 9,
  24, 10,
  48, 11,
  96, 12,
  36, 13,
  8, 14,
  16, 15
};

/* frequency limit of rectangle channels */
static const int freq_limit[8] = {
  0x3FF, 0x555, 0x666, 0x71C, 0x787, 0x7C1, 0x7E0, 0x7F0
};

/* noise frequency lookup table */
static const int noise_freq[16] = {
  4, 8, 16, 32, 64, 96, 128, 160,
  202, 254, 380, 508, 762, 1016, 2034, 4068
};

/* DMC transfer freqs */
const int dmc_clocks[16] = {
  428, 380, 340, 320, 286, 254, 226, 214,
  190, 160, 142, 128, 106, 85, 72, 54
};

/* ratios of pos/neg pulse for rectangle waves */
static const int duty_lut[4] = { 2, 4, 8, 12 };


void
apu_setcontext (apu_t * src_apu)
{
  apu = src_apu;
  /* $$$ ben reset eoor string here. */
  SET_APU_ERROR (apu, "no error");
}

/*
** Simple queue routines
*/
#define  APU_QEMPTY()   (apu->q_head == apu->q_tail)

static int
apu_enqueue (apudata_t * d)
{
  ASSERT (apu);
  apu->queue[apu->q_head] = *d;

  apu->q_head = (apu->q_head + 1) & APUQUEUE_MASK;

  if (APU_QEMPTY ()) {
    log_printf ("apu: queue overflow\n");
    SET_APU_ERROR (apu, "queue overflow");
    return -1;
  }
  return 0;
}

static apudata_t *
apu_dequeue (void)
{
  int loc;

  ASSERT (apu);

  if (APU_QEMPTY ()) {
    log_printf ("apu: queue empty\n");
    SET_APU_ERROR (apu, "queue empty");
    /* $$$ ben : should return 0 ??? */
  }
  loc = apu->q_tail;
  apu->q_tail = (apu->q_tail + 1) & APUQUEUE_MASK;

  return &apu->queue[loc];
}

int
apu_setchan (int chan, boolean enabled)
{
  const unsigned int max = 6;
  int old;

  ASSERT (apu);
  if ((unsigned int) chan >= max) {
    SET_APU_ERROR (apu, "channel out of range");
    return -1;
  }
  old = (apu->mix_enable >> chan) & 1;
  if (enabled != (boolean) - 1) {
    apu->mix_enable = (apu->mix_enable & ~(1 << chan)) | ((! !enabled) << chan);
  }
  return old;
}

/* emulation of the 15-bit shift register the
** NES uses to generate pseudo-random series
** for the white noise channel
*/
#ifdef REALTIME_NOISE
INLINE int8
shift_register15 (uint8 xor_tap)
{
  static int sreg = 0x4000;
  int bit0, tap, bit14;

  bit0 = sreg & 1;
  tap = (sreg & xor_tap) ? 1 : 0;
  bit14 = (bit0 ^ tap);
  sreg >>= 1;
  sreg |= (bit14 << 14);
  return (bit0 ^ 1);
}
#else
static void
shift_register15 (int8 * buf, int count)
{
  static int sreg = 0x4000;
  int bit0, bit1, bit6, bit14;

  if (count == APU_NOISE_93) {
    while (count--) {
      bit0 = sreg & 1;
      bit6 = (sreg & 0x40) >> 6;
      bit14 = (bit0 ^ bit6);
      sreg >>= 1;
      sreg |= (bit14 << 14);
      *buf++ = bit0 ^ 1;
    }
  } else {                      /* 32K noise */

    while (count--) {
      bit0 = sreg & 1;
      bit1 = (sreg & 2) >> 1;
      bit14 = (bit0 ^ bit1);
      sreg >>= 1;
      sreg |= (bit14 << 14);
      *buf++ = bit0 ^ 1;
    }
  }
}
#endif

/* RECTANGLE WAVE
** ==============
** reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle
** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on
** reg2: 8 bits of freq
** reg3: 0-2=high freq, 7-4=vbl length counter
*/
#define  APU_RECTANGLE_OUTPUT chan->output_vol
static int32
apu_rectangle (rectangle_t * chan)
{
  int32 output;

#ifdef APU_OVERSAMPLE
  int num_times;
  int32 total;
#endif

  APU_VOLUME_DECAY (chan->output_vol);

  if (FALSE == chan->enabled || 0 == chan->vbl_length)
    return APU_RECTANGLE_OUTPUT;

  /* vbl length counter */
  if (FALSE == chan->holdnote)
    chan->vbl_length--;

  /* envelope decay at a rate of (env_delay + 1) / 240 secs */
  chan->env_phase -= 4;         /* 240/60 */
  while (chan->env_phase < 0) {
    chan->env_phase += chan->env_delay;

    if (chan->holdnote)
      chan->env_vol = (chan->env_vol + 1) & 0x0F;
    else if (chan->env_vol < 0x0F)
      chan->env_vol++;
  }

  if ((FALSE == chan->sweep_inc && chan->freq > chan->freq_limit)
      || chan->freq < APU_TO_FIXED (4))
    return APU_RECTANGLE_OUTPUT;

  /* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */
  if (chan->sweep_on && chan->sweep_shifts) {
    chan->sweep_phase -= 2;     /* 120/60 */
    while (chan->sweep_phase < 0) {
      chan->sweep_phase += chan->sweep_delay;
      if (chan->sweep_inc)      /* ramp up */
        chan->freq -= chan->freq >> (chan->sweep_shifts);
      else                      /* ramp down */
        chan->freq += chan->freq >> (chan->sweep_shifts);
    }
  }

  chan->phaseacc -= apu->cycle_rate;    /* # of cycles per sample */
  if (chan->phaseacc >= 0)
    return APU_RECTANGLE_OUTPUT;

#ifdef APU_OVERSAMPLE
  num_times = total = 0;

  if (chan->fixed_envelope)
    output = chan->volume << 8; /* fixed volume */
  else
    output = (chan->env_vol ^ 0x0F) << 8;
#endif

  while (chan->phaseacc < 0) {
    chan->phaseacc += chan->freq;
    chan->adder = (chan->adder + 1) & 0x0F;

#ifdef APU_OVERSAMPLE
    if (chan->adder < chan->duty_flip)
      total += output;
    else
      total -= output;

    num_times++;
#endif
  }

#ifdef APU_OVERSAMPLE
  chan->output_vol = total / num_times;
#else
  if (chan->fixed_envelope)
    output = chan->volume << 8; /* fixed volume */
  else
    output = (chan->env_vol ^ 0x0F) << 8;

  if (0 == chan->adder)
    chan->output_vol = output;
  else if (chan->adder == chan->duty_flip)
    chan->output_vol = -output;
#endif

  return APU_RECTANGLE_OUTPUT;
}

/* TRIANGLE WAVE
** =============
** reg0: 7=holdnote, 6-0=linear length counter
** reg2: low 8 bits of frequency
** reg3: 7-3=length counter, 2-0=high 3 bits of frequency
*/
#define  APU_TRIANGLE_OUTPUT  (chan->output_vol + (chan->output_vol >> 2))
static int32
apu_triangle (triangle_t * chan)
{
  APU_VOLUME_DECAY (chan->output_vol);

  if (FALSE == chan->enabled || 0 == chan->vbl_length)
    return APU_TRIANGLE_OUTPUT;

  if (chan->counter_started) {
    if (chan->linear_length > 0)
      chan->linear_length--;
    if (chan->vbl_length && FALSE == chan->holdnote)
      chan->vbl_length--;
  } else if (FALSE == chan->holdnote && chan->write_latency) {
    if (--chan->write_latency == 0)
      chan->counter_started = TRUE;
  }
/*
   if (chan->countmode == COUNTMODE_COUNT)
   {
      if (chan->linear_length > 0)
         chan->linear_length--;
      if (chan->vbl_length)
         chan->vbl_length--;
   }
*/
  if (0 == chan->linear_length || chan->freq < APU_TO_FIXED (4))        /* inaudible */
    return APU_TRIANGLE_OUTPUT;

  chan->phaseacc -= apu->cycle_rate;    /* # of cycles per sample */
  while (chan->phaseacc < 0) {
    chan->phaseacc += chan->freq;
    chan->adder = (chan->adder + 1) & 0x1F;

    if (chan->adder & 0x10)
      chan->output_vol -= (2 << 8);
    else
      chan->output_vol += (2 << 8);
  }

  return APU_TRIANGLE_OUTPUT;
}


/* WHITE NOISE CHANNEL
** ===================
** reg0: 0-3=volume, 4=envelope, 5=hold
** reg2: 7=small(93 byte) sample,3-0=freq lookup
** reg3: 7-4=vbl length counter
*/
#define  APU_NOISE_OUTPUT  ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2)

static int32
apu_noise (noise_t * chan)
{
  int32 outvol;

#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE)
#else
  int32 noise_bit;
#endif
#ifdef APU_OVERSAMPLE
  int num_times;
  int32 total;
#endif

  APU_VOLUME_DECAY (chan->output_vol);

  if (FALSE == chan->enabled || 0 == chan->vbl_length)
    return APU_NOISE_OUTPUT;

  /* vbl length counter */
  if (FALSE == chan->holdnote)
    chan->vbl_length--;

  /* envelope decay at a rate of (env_delay + 1) / 240 secs */
  chan->env_phase -= 4;         /* 240/60 */
  while (chan->env_phase < 0) {
    chan->env_phase += chan->env_delay;

    if (chan->holdnote)
      chan->env_vol = (chan->env_vol + 1) & 0x0F;
    else if (chan->env_vol < 0x0F)
      chan->env_vol++;
  }

  chan->phaseacc -= apu->cycle_rate;    /* # of cycles per sample */
  if (chan->phaseacc >= 0)
    return APU_NOISE_OUTPUT;

#ifdef APU_OVERSAMPLE
  num_times = total = 0;

  if (chan->fixed_envelope)
    outvol = chan->volume << 8; /* fixed volume */
  else
    outvol = (chan->env_vol ^ 0x0F) << 8;
#endif

  while (chan->phaseacc < 0) {
    chan->phaseacc += chan->freq;

#ifdef REALTIME_NOISE

#ifdef APU_OVERSAMPLE
    if (shift_register15 (chan->xor_tap))
      total += outvol;
    else
      total -= outvol;

    num_times++;
#else
    noise_bit = shift_register15 (chan->xor_tap);
#endif

#else
    chan->cur_pos++;

    if (chan->short_sample) {
      if (APU_NOISE_93 == chan->cur_pos)
        chan->cur_pos = 0;
    } else {
      if (APU_NOISE_32K == chan->cur_pos)
        chan->cur_pos = 0;
    }

#ifdef APU_OVERSAMPLE
    if (chan->short_sample)
      noise_bit = noise_short_lut[chan->cur_pos];
    else
      noise_bit = noise_long_lut[chan->cur_pos];

    if (noise_bit)
      total += outvol;
    else
      total -= outvol;

    num_times++;
#endif
#endif /* REALTIME_NOISE */
  }

#ifdef APU_OVERSAMPLE
  chan->output_vol = total / num_times;
#else
  if (chan->fixed_envelope)
    outvol = chan->volume << 8; /* fixed volume */
  else
    outvol = (chan->env_vol ^ 0x0F) << 8;

#ifndef REALTIME_NOISE
  if (chan->short_sample)
    noise_bit = noise_short_lut[chan->cur_pos];
  else
    noise_bit = noise_long_lut[chan->cur_pos];
#endif /* !REALTIME_NOISE */

  if (noise_bit)
    chan->output_vol = outvol;
  else
    chan->output_vol = -outvol;
#endif

  return APU_NOISE_OUTPUT;
}


INLINE void
apu_dmcreload (dmc_t * chan)
{
  chan->address = chan->cached_addr;
  chan->dma_length = chan->cached_dmalength;
  chan->irq_occurred = FALSE;
}

/* DELTA MODULATION CHANNEL
** =========================
** reg0: 7=irq gen, 6=looping, 3-0=pointer to clock table
** reg1: output dc level, 6 bits unsigned
** reg2: 8 bits of 64-byte aligned address offset : $C000 + (value * 64)
** reg3: length, (value * 16) + 1
*/
#define  APU_DMC_OUTPUT ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2)
static int32
apu_dmc (dmc_t * chan)
{
  int delta_bit;

  APU_VOLUME_DECAY (chan->output_vol);

  /* only process when channel is alive */
  if (chan->dma_length) {
    chan->phaseacc -= apu->cycle_rate;  /* # of cycles per sample */

    while (chan->phaseacc < 0) {
      chan->phaseacc += chan->freq;

      delta_bit = (chan->dma_length & 7) ^ 7;

      if (7 == delta_bit) {
        chan->cur_byte = nes6502_getbyte (chan->address);

        /* steal a cycle from CPU */
        nes6502_setdma (1);

        if (0xFFFF == chan->address)
          chan->address = 0x8000;
        else
          chan->address++;
      }

      if (--chan->dma_length == 0) {
        /* if loop bit set, we're cool to retrigger sample */
        if (chan->looping)
          apu_dmcreload (chan);
        else {
          /* check to see if we should generate an irq */
          if (chan->irq_gen) {
            chan->irq_occurred = TRUE;
            nes6502_irq ();
          }

          /* bodge for timestamp queue */
          chan->enabled = FALSE;
          break;
        }
      }

      /* positive delta */
      if (chan->cur_byte & (1 << delta_bit)) {
        if (chan->regs[1] < 0x7D) {
          chan->regs[1] += 2;
          chan->output_vol += (2 << 8);
        }
/*
            if (chan->regs[1] < 0x3F)
               chan->regs[1]++;

            chan->output_vol &= ~(0x7E << 8);
            chan->output_vol |= ((chan->regs[1] << 1) << 8);
*/
      }
      /* negative delta */
      else {
        if (chan->regs[1] > 1) {
          chan->regs[1] -= 2;
          chan->output_vol -= (2 << 8);
        }

/*
            if (chan->regs[1] > 0)
               chan->regs[1]--;

            chan->output_vol &= ~(0x7E << 8);
            chan->output_vol |= ((chan->regs[1] << 1) << 8);
*/
      }
    }
  }

  return APU_DMC_OUTPUT;
}


static void
apu_regwrite (uint32 address, uint8 value)
{
  int chan;

  ASSERT (apu);
  switch (address) {
      /* rectangles */
    case APU_WRA0:
    case APU_WRB0:
      chan = (address & 4) ? 1 : 0;
      apu->rectangle[chan].regs[0] = value;

      apu->rectangle[chan].volume = value & 0x0F;
      apu->rectangle[chan].env_delay = decay_lut[value & 0x0F];
      apu->rectangle[chan].holdnote = (value & 0x20) ? TRUE : FALSE;
      apu->rectangle[chan].fixed_envelope = (value & 0x10) ? TRUE : FALSE;
      apu->rectangle[chan].duty_flip = duty_lut[value >> 6];
      break;

    case APU_WRA1:
    case APU_WRB1:
      chan = (address & 4) ? 1 : 0;
      apu->rectangle[chan].regs[1] = value;
      apu->rectangle[chan].sweep_on = (value & 0x80) ? TRUE : FALSE;
      apu->rectangle[chan].sweep_shifts = value & 7;
      apu->rectangle[chan].sweep_delay = decay_lut[(value >> 4) & 7];

      apu->rectangle[chan].sweep_inc = (value & 0x08) ? TRUE : FALSE;
      apu->rectangle[chan].freq_limit = APU_TO_FIXED (freq_limit[value & 7]);
      break;

    case APU_WRA2:
    case APU_WRB2:
      chan = (address & 4) ? 1 : 0;
      apu->rectangle[chan].regs[2] = value;
/*      if (apu->rectangle[chan].enabled) */
      apu->rectangle[chan].freq =
          APU_TO_FIXED ((((apu->rectangle[chan].regs[3] & 7) << 8) + value) +
          1);
      break;

    case APU_WRA3:
    case APU_WRB3:
      chan = (address & 4) ? 1 : 0;
      apu->rectangle[chan].regs[3] = value;

/*      if (apu->rectangle[chan].enabled) */
      {
        apu->rectangle[chan].vbl_length = vbl_lut[value >> 3];
        apu->rectangle[chan].env_vol = 0;
        apu->rectangle[chan].freq =
            APU_TO_FIXED ((((value & 7) << 8) + apu->rectangle[chan].regs[2]) +
            1);
        apu->rectangle[chan].adder = 0;
      }
      break;

      /* triangle */
    case APU_WRC0:
/*
      if (0 == (apu->triangle.regs[0] & 0x80))
         apu->triangle.countmode = COUNTMODE_COUNT;
      else
      {
         if (apu->triangle.countmode == COUNTMODE_LOAD && apu->triangle.vbl_length)
            apu->triangle.linear_length = trilength_lut[value & 0x7F];

         if (0 == (value & 0x80))
            apu->triangle.countmode = COUNTMODE_COUNT;
      }
*/
      apu->triangle.regs[0] = value;

      apu->triangle.holdnote = (value & 0x80) ? TRUE : FALSE;


/*      if (apu->triangle.enabled) */
      {
        if (FALSE == apu->triangle.counter_started && apu->triangle.vbl_length)
          apu->triangle.linear_length = trilength_lut[value & 0x7F];
      }

      break;

    case APU_WRC2:

      apu->triangle.regs[1] = value;

/*      if (apu->triangle.enabled) */
      apu->triangle.freq =
          APU_TO_FIXED ((((apu->triangle.regs[2] & 7) << 8) + value) + 1);
      break;

    case APU_WRC3:

      apu->triangle.regs[2] = value;

      /* this is somewhat of a hack.  there appears to be some latency on 
       ** the Real Thing between when trireg0 is written to and when the 
       ** linear length counter actually begins its countdown.  we want to 
       ** prevent the case where the program writes to the freq regs first, 
       ** then to reg 0, and the counter accidentally starts running because 
       ** of the sound queue's timestamp processing.
       **
       ** set latency to a couple scanlines -- should be plenty of time for 
       ** the 6502 code to do a couple of table dereferences and load up the
       ** other triregs
       */

      /* 06/13/00 MPC -- seems to work OK */
      apu->triangle.write_latency =
          (int) (2 * NES_SCANLINE_CYCLES / APU_FROM_FIXED (apu->cycle_rate));
/*
      apu->triangle.linear_length = trilength_lut[apu->triangle.regs[0] & 0x7F];
      if (0 == (apu->triangle.regs[0] & 0x80))
         apu->triangle.countmode = COUNTMODE_COUNT;
      else
         apu->triangle.countmode = COUNTMODE_LOAD;
*/
/*      if (apu->triangle.enabled) */
      {
        apu->triangle.freq =
            APU_TO_FIXED ((((value & 7) << 8) + apu->triangle.regs[1]) + 1);
        apu->triangle.vbl_length = vbl_lut[value >> 3];
        apu->triangle.counter_started = FALSE;
        apu->triangle.linear_length =
            trilength_lut[apu->triangle.regs[0] & 0x7F];
      }

      break;

      /* noise */
    case APU_WRD0:
      apu->noise.regs[0] = value;
      apu->noise.env_delay = decay_lut[value & 0x0F];
      apu->noise.holdnote = (value & 0x20) ? TRUE : FALSE;
      apu->noise.fixed_envelope = (value & 0x10) ? TRUE : FALSE;
      apu->noise.volume = value & 0x0F;
      break;

    case APU_WRD2:
      apu->noise.regs[1] = value;
      apu->noise.freq = APU_TO_FIXED (noise_freq[value & 0x0F]);

#ifdef REALTIME_NOISE
      apu->noise.xor_tap = (value & 0x80) ? 0x40 : 0x02;
#else
      /* detect transition from long->short sample */
      if ((value & 0x80) && FALSE == apu->noise.short_sample) {
        /* recalculate short noise buffer */
        shift_register15 (noise_short_lut, APU_NOISE_93);
        apu->noise.cur_pos = 0;
      }
      apu->noise.short_sample = (value & 0x80) ? TRUE : FALSE;
#endif
      break;

    case APU_WRD3:
      apu->noise.regs[2] = value;

/*      if (apu->noise.enabled) */
      {
        apu->noise.vbl_length = vbl_lut[value >> 3];
        apu->noise.env_vol = 0; /* reset envelope */
      }
      break;

      /* DMC */
    case APU_WRE0:
      apu->dmc.regs[0] = value;

      apu->dmc.freq = APU_TO_FIXED (dmc_clocks[value & 0x0F]);
      apu->dmc.looping = (value & 0x40) ? TRUE : FALSE;

      if (value & 0x80)
        apu->dmc.irq_gen = TRUE;
      else {
        apu->dmc.irq_gen = FALSE;
        apu->dmc.irq_occurred = FALSE;
      }
      break;

    case APU_WRE1:             /* 7-bit DAC */
      /* add the _delta_ between written value and
       ** current output level of the volume reg
       */
      value &= 0x7F;            /* bit 7 ignored */
      apu->dmc.output_vol += ((value - apu->dmc.regs[1]) << 8);
      apu->dmc.regs[1] = value;
/*      
      apu->dmc.output_vol = (value & 0x7F) << 8;
      apu->dmc.regs[1] = (value & 0x7E) >> 1;
*/
      break;

    case APU_WRE2:
      apu->dmc.regs[2] = value;
      apu->dmc.cached_addr = 0xC000 + (uint16) (value << 6);
      break;

    case APU_WRE3:
      apu->dmc.regs[3] = value;
      apu->dmc.cached_dmalength = ((value << 4) + 1) << 3;
      break;

    case APU_SMASK:
      /* bodge for timestamp queue */
      apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE;

      apu->enable_reg = value;

      for (chan = 0; chan < 2; chan++) {
        if (value & (1 << chan))
          apu->rectangle[chan].enabled = TRUE;
        else {
          apu->rectangle[chan].enabled = FALSE;
          apu->rectangle[chan].vbl_length = 0;
        }
      }

      if (value & 0x04)
        apu->triangle.enabled = TRUE;
      else {
        apu->triangle.enabled = FALSE;
        apu->triangle.vbl_length = 0;
        apu->triangle.linear_length = 0;
        apu->triangle.counter_started = FALSE;
        apu->triangle.write_latency = 0;
      }

      if (value & 0x08)
        apu->noise.enabled = TRUE;
      else {
        apu->noise.enabled = FALSE;
        apu->noise.vbl_length = 0;
      }

      if (value & 0x10) {
        if (0 == apu->dmc.dma_length)
          apu_dmcreload (&apu->dmc);
      } else
        apu->dmc.dma_length = 0;

      apu->dmc.irq_occurred = FALSE;
      break;

      /* unused, but they get hit in some mem-clear loops */
    case 0x4009:
    case 0x400D:
      break;

    default:
      break;
  }
}

/* Read from $4000-$4017 */
uint8
apu_read (uint32 address)
{
  uint8 value;

  ASSERT (apu);

  switch (address) {
    case APU_SMASK:
      /* seems that bit 6 denotes vblank -- return 1 for now */
      value = 0x40;

      /* Return 1 in 0-5 bit pos if a channel is playing */
      if (apu->rectangle[0].enabled && apu->rectangle[0].vbl_length)
        value |= 0x01;
      if (apu->rectangle[1].enabled && apu->rectangle[1].vbl_length)
        value |= 0x02;
      if (apu->triangle.enabled && apu->triangle.vbl_length)
        value |= 0x04;
      if (apu->noise.enabled && apu->noise.vbl_length)
        value |= 0x08;

      /* if (apu->dmc.dma_length) */
      /* bodge for timestamp queue */
      if (apu->dmc.enabled)
        value |= 0x10;

      if (apu->dmc.irq_occurred)
        value |= 0x80;

      break;

#ifndef NSF_PLAYER
    case APU_JOY0:
      value = input_get (INP_JOYPAD0);
      break;

    case APU_JOY1:
      value = input_get (INP_ZAPPER | INP_JOYPAD1
          /*| INP_ARKANOID *//*| INP_POWERPAD */ );
      break;
#endif /* !NSF_PLAYER */

    default:
      value = (address >> 8);   /* heavy capacitance on data bus */
      break;
  }

  return value;
}


void
apu_write (uint32 address, uint8 value)
{
#ifndef NSF_PLAYER
  static uint8 last_write;
#endif /* !NSF_PLAYER */
  apudata_t d;

  switch (address) {
    case 0x4015:
      /* bodge for timestamp queue */
      apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE;

    case 0x4000:
    case 0x4001:
    case 0x4002:
    case 0x4003:
    case 0x4004:
    case 0x4005:
    case 0x4006:
    case 0x4007:
    case 0x4008:
    case 0x4009:
    case 0x400A:
    case 0x400B:
    case 0x400C:
    case 0x400D:
    case 0x400E:
    case 0x400F:
    case 0x4010:
    case 0x4011:
    case 0x4012:
    case 0x4013:
      d.timestamp = nes6502_getcycles (FALSE);
      d.address = address;
      d.value = value;
      apu_enqueue (&d);
      break;

#ifndef NSF_PLAYER
    case APU_OAMDMA:
      ppu_oamdma (address, value);
      break;

    case APU_JOY0:
      /* VS system VROM switching */
      mmc_vsvrom (value & 4);

      /* see if we need to strobe them joypads */
      value &= 1;
      if ((0 == value) && last_write)
        input_strobe ();
      last_write = value;
      break;

    case APU_JOY1:             /* Some kind of IRQ control business */
      break;

#endif /* !NSF_PLAYER */

    default:
      break;
  }
}

void
apu_getpcmdata (void **data, int *num_samples, int *sample_bits)
{
  ASSERT (apu);
  *data = apu->buffer;
  *num_samples = apu->num_samples;
  *sample_bits = apu->sample_bits;
}


void
apu_process (uint8 * buffer, int num_samples)
{
  apudata_t *d;
  uint32 elapsed_cycles;
  static int32 prev_sample = 0;
  int32 next_sample, accum;

  ASSERT (apu);

  /* grab it, keep it local for speed */
  elapsed_cycles = (uint32) apu->elapsed_cycles;

  /* BLEH */
  apu->buffer = buffer;

  while (num_samples--) {
    while ((FALSE == APU_QEMPTY ())
        && (apu->queue[apu->q_tail].timestamp <= elapsed_cycles)) {
      d = apu_dequeue ();
      apu_regwrite (d->address, d->value);
    }

    elapsed_cycles += APU_FROM_FIXED (apu->cycle_rate);

    accum = 0;
    if (APU_MIX_ENABLE (0))
      accum += apu_rectangle (&apu->rectangle[0]);
    if (APU_MIX_ENABLE (1))
      accum += apu_rectangle (&apu->rectangle[1]);
    if (APU_MIX_ENABLE (2))
      accum += apu_triangle (&apu->triangle);
    if (APU_MIX_ENABLE (3))
      accum += apu_noise (&apu->noise);
    if (APU_MIX_ENABLE (4))
      accum += apu_dmc (&apu->dmc);

    if (apu->ext && APU_MIX_ENABLE (5))
      accum += apu->ext->process ();

    /* do any filtering */
    if (APU_FILTER_NONE != apu->filter_type) {
      next_sample = accum;

      if (APU_FILTER_LOWPASS == apu->filter_type) {
        accum += prev_sample;
        accum >>= 1;
      } else
        accum = (accum + accum + accum + prev_sample) >> 2;

      prev_sample = next_sample;
    }

    /* little extra kick for the kids */
    accum <<= 1;

    /* prevent clipping */
    if (accum > 0x7FFF)
      accum = 0x7FFF;
    else if (accum < -0x8000)
      accum = -0x8000;

    /* signed 16-bit output, unsigned 8-bit */
    if (16 == apu->sample_bits) {
      *(int16 *) (buffer) = (int16) accum;
      buffer += sizeof (int16);
    } else {
      *(uint8 *) (buffer) = (accum >> 8) ^ 0x80;
      buffer += sizeof (uint8);
    }
  }

  /* resync cycle counter */
  apu->elapsed_cycles = nes6502_getcycles (FALSE);
}

/* set the filter type */
/* $$$ ben :
 * Add a get feature (filter_type == -1) and returns old filter type
 */
int
apu_setfilter (int filter_type)
{
  int old;

  ASSERT (apu);
  old = apu->filter_type;
  if (filter_type != -1) {
    apu->filter_type = filter_type;
  }
  return old;
}

void
apu_reset (void)
{
  uint32 address;

  ASSERT (apu);

  apu->elapsed_cycles = 0;
  memset (&apu->queue, 0, APUQUEUE_SIZE * sizeof (apudata_t));
  apu->q_head = 0;
  apu->q_tail = 0;

  /* use to avoid bugs =) */
  for (address = 0x4000; address <= 0x4013; address++)
    apu_regwrite (address, 0);

#ifdef NSF_PLAYER
  apu_regwrite (0x400C, 0x10);  /* silence noise channel on NSF start */
  apu_regwrite (0x4015, 0x0F);
#else
  apu_regwrite (0x4015, 0);
#endif /* NSF_PLAYER */

  if (apu->ext)
    apu->ext->reset ();
}

static void
apu_build_luts (int num_samples)
{
  int i;

  /* lut used for enveloping and frequency sweeps */
  for (i = 0; i < 16; i++)
    decay_lut[i] = num_samples * (i + 1);

  /* used for note length, based on vblanks and size of audio buffer */
  for (i = 0; i < 32; i++)
    vbl_lut[i] = vbl_length[i] * num_samples;

  /* triangle wave channel's linear length table */
  for (i = 0; i < 128; i++)
    trilength_lut[i] = (i * num_samples) / 4;

#ifndef REALTIME_NOISE
  /* generate noise samples */
  shift_register15 (noise_long_lut, APU_NOISE_32K);
  shift_register15 (noise_short_lut, APU_NOISE_93);
#endif /* !REALTIME_NOISE */
}

static void
apu_setactive (apu_t * active)
{
  ASSERT (active);
  apu = active;
}

/* Initializes emulated sound hardware, creates waveforms/voices */
apu_t *
apu_create (int sample_rate, int refresh_rate, int sample_bits, boolean stereo)
{
  apu_t *temp_apu;

/*    int channel; */

  temp_apu = malloc (sizeof (apu_t));
  if (NULL == temp_apu)
    return NULL;
  /* $$$ ben : safety net, in case we forgot to init something */
  memset (temp_apu, 0, sizeof (apu_t));

  SET_APU_ERROR (temp_apu, "no error");
  temp_apu->sample_rate = sample_rate;
  temp_apu->refresh_rate = refresh_rate;
  temp_apu->sample_bits = sample_bits;

  temp_apu->num_samples = sample_rate / refresh_rate;
  /* turn into fixed point! */
  temp_apu->cycle_rate = (int32) (APU_BASEFREQ * 65536.0 / (float) sample_rate);

  /* build various lookup tables for apu */
  apu_build_luts (temp_apu->num_samples);

  /* set the update routine */
  temp_apu->process = apu_process;
  temp_apu->ext = NULL;

  apu_setactive (temp_apu);
  apu_reset ();

  temp_apu->mix_enable = 0x3F;
/*    for (channel = 0; channel < 6; channel++) */
/*       apu_setchan(channel, TRUE); */

  apu_setfilter (APU_FILTER_LOWPASS);

  return temp_apu;
}

apu_t *
apu_getcontext (void)
{
  return apu;
}

void
apu_destroy (apu_t * src_apu)
{
  if (src_apu) {
    if (src_apu->ext)
      src_apu->ext->shutdown ();
    free (src_apu);
  }
}

int
apu_setext (apu_t * src_apu, apuext_t * ext)
{
  ASSERT (src_apu);

  /* $$$ ben : seem cleaner like this */
  if (src_apu->ext) {
    src_apu->ext->shutdown ();
  }

  src_apu->ext = ext;

  /* initialize it */
  if (src_apu->ext)
    src_apu->ext->init ();

  /* $$$ ben : May be one day extension int () will return error code */
  return 0;
}

/* this exists for external mixing routines */
int32
apu_getcyclerate (void)
{
  ASSERT (apu);
  return apu->cycle_rate;
}

/*
** $Log$
** Revision 1.3  2008/04/09 13:43:50  thaytan
** * gst/nsf/nes_apu.c: (apu_process):
** * gst/nsf/nes_apu.h:
** Don't do void pointer arithmetic - it's a gcc extension.
**
** Revision 1.2  2008-03-25 15:56:12  slomo
** Patch by: Andreas Henriksson <andreas at fatal dot set>
** * gst/nsf/Makefile.am:
** * gst/nsf/dis6502.h:
** * gst/nsf/fds_snd.c:
** * gst/nsf/fds_snd.h:
** * gst/nsf/fmopl.c:
** * gst/nsf/fmopl.h:
** * gst/nsf/gstnsf.c:
** * gst/nsf/log.c:
** * gst/nsf/log.h:
** * gst/nsf/memguard.c:
** * gst/nsf/memguard.h:
** * gst/nsf/mmc5_snd.c:
** * gst/nsf/mmc5_snd.h:
** * gst/nsf/nes6502.c:
** * gst/nsf/nes6502.h:
** * gst/nsf/nes_apu.c:
** * gst/nsf/nes_apu.h:
** * gst/nsf/nsf.c:
** * gst/nsf/nsf.h:
** * gst/nsf/osd.h:
** * gst/nsf/types.h:
** * gst/nsf/vrc7_snd.c:
** * gst/nsf/vrc7_snd.h:
** * gst/nsf/vrcvisnd.c:
** * gst/nsf/vrcvisnd.h:
** Update our internal nosefart to nosefart-2.7-mls to fix segfaults
** on some files. Fixes bug #498237.
** Remove some // comments, fix some compiler warnings and use pow()
** instead of a slow, selfmade implementation.
**
** Revision 1.2  2003/04/09 14:50:32  ben
** Clean NSF api.
**
** Revision 1.1  2003/04/08 20:53:01  ben
** Adding more files...
**
** Revision 1.19  2000/07/04 04:53:26  matt
** minor changes, sound amplification
**
** Revision 1.18  2000/07/03 02:18:53  matt
** much better external module exporting
**
** Revision 1.17  2000/06/26 11:01:55  matt
** made triangle a tad quieter
**
** Revision 1.16  2000/06/26 05:10:33  matt
** fixed cycle rate generation accuracy
**
** Revision 1.15  2000/06/26 05:00:37  matt
** cleanups
**
** Revision 1.14  2000/06/23 11:06:24  matt
** more faithful mixing of channels
**
** Revision 1.13  2000/06/23 03:29:27  matt
** cleaned up external sound inteface
**
** Revision 1.12  2000/06/20 00:08:39  matt
** bugfix to rectangle wave
**
** Revision 1.11  2000/06/13 13:48:58  matt
** fixed triangle write latency for fixed point apu cycle rate
**
** Revision 1.10  2000/06/12 01:14:36  matt
** minor change to clipping extents
**
** Revision 1.9  2000/06/09 20:00:56  matt
** fixed noise hiccup in NSF player mode
**
** Revision 1.8  2000/06/09 16:49:02  matt
** removed all floating point from sound generation
**
** Revision 1.7  2000/06/09 15:12:28  matt
** initial revision
**
*/
