/*
 * Purpose: Virtual mixing audio driver output mixing routines
 *
 * This file contains the actual mixing and resampling engine for output.
 * The actual algorithms are implemented in outexport.inc and playmix.inc.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "vmix_cfg.h"
#include "vmix.h"

#if 0
/* TODO: Disable this debugging stuff */
extern unsigned char tmp_status;
# define UP_STATUS(v) OUTB(NULL, (tmp_status=tmp_status|(v)), 0x378)
# define DOWN_STATUS(v) OUTB(NULL, (tmp_status=tmp_status&~(v)), 0x378)
#else
# define UP_STATUS(v)
# define DOWN_STATUS(v)
#endif

#undef  SINE_DEBUG

#ifdef SINE_DEBUG
#define SINE_SIZE	48
static const float sine_table[SINE_SIZE] = {
  0.000000, 0.130526, 0.258819, 0.382683,
  0.500000, 0.608761, 0.707107, 0.793353,
  0.866025, 0.923880, 0.965926, 0.991445,
  1.000000, 0.991445, 0.965926, 0.923880,
  0.866025, 0.793353, 0.707107, 0.608761,
  0.500000, 0.382683, 0.258819, 0.130526,
  0.000000, -0.130526, -0.258819, -0.382683,
  -0.500000, -0.608761, -0.707107, -0.793353,
  -0.866025, -0.923880, -0.965926, -0.991445,
  -1.000000, -0.991445, -0.965926, -0.923880,
  -0.866025, -0.793353, -0.707107, -0.608761,
  -0.500000, -0.382683, -0.258819, -0.130526
};
static int sine_phase[MAX_PLAY_CHANNELS] = { 0 };
#endif

/*
 * Output export functions
 */

static void
export16ne (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  short *outp;
  short *op;
#define SAMPLE_TYPE	short
#define SAMPLE_RANGE	32768.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x

#include "outexport.inc"
}

static void
export16oe (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  short *outp;
  short *op;
#undef  SAMPLE_TYPE
#undef  SAMPLE_RANGE
#define SAMPLE_TYPE	short
#define SAMPLE_RANGE	32768.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap16(x)

#include "outexport.inc"
}

static void
export32ne (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  int *outp;
  int *op;
#undef  SAMPLE_TYPE
#undef  SAMPLE_RANGE
#define SAMPLE_TYPE	int
#define SAMPLE_RANGE	2147483648.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x

#include "outexport.inc"
}

static void
export32oe (vmix_engine_t * eng, void *outbuf, vmix_sample_t * chbufs[],
	    int channels, int samples)
{
  int *outp;
  int *op;
#define SAMPLE_TYPE	int
#define SAMPLE_RANGE	2147483648.0
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap32(x)

#include "outexport.inc"
}

/*
 * Mixing functions
 */
#undef  BUFFER_TYPE
#define BUFFER_TYPE short *

void
vmix_outmix_16ne (vmix_portc_t * portc, int nsamples)
{
  short *inp;
  float range = 3.0517578125e-5;	/* 1.0 / 32768.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix.inc"
}

void
vmix_outmix_16oe (vmix_portc_t * portc, int nsamples)
{
  short *inp;
  float range = 3.0517578125e-5;	/* 1.0 / 32768.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap16(x)
#include "playmix.inc"
}

#undef BUFFER_TYPE
#define BUFFER_TYPE int *

void
vmix_outmix_32ne (vmix_portc_t * portc, int nsamples)
{
  int *inp;
  double range = 4.65661287308e-10;	/* 1.0 / 2147483648.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix.inc"
}

void
vmix_outmix_32oe (vmix_portc_t * portc, int nsamples)
{
  int *inp;
  double range = 4.65661287308e-10;	/* 1.0 / 2147483648.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap32(x)
#include "playmix.inc"
}

void
vmix_outmix_float (vmix_portc_t * portc, int nsamples)
{
  float *inp;
  double range = 1.0;
#undef BUFFER_TYPE
#define BUFFER_TYPE float *
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix.inc"
}

/*
 * Mixing functions (with sample rate conversions)
 */
#undef  BUFFER_TYPE
#define BUFFER_TYPE short *

void
vmix_outmix_16ne_src (vmix_portc_t * portc, int nsamples)
{
  short *inp;
  float range = 3.0517578125e-5;	/* 1.0 / 32768.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix_src.inc"
}

void
vmix_outmix_16oe_src (vmix_portc_t * portc, int nsamples)
{
  short *inp;
  float range = 3.0517578125e-5;	/* 1.0 / 32768.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap16(x)
#include "playmix_src.inc"
}

#undef BUFFER_TYPE
#define BUFFER_TYPE int *

void
vmix_outmix_32ne_src (vmix_portc_t * portc, int nsamples)
{
  int *inp;
  double range = 4.65661287308e-10;	/* 1.0 / 2147483648.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix_src.inc"
}

void
vmix_outmix_32oe_src (vmix_portc_t * portc, int nsamples)
{
  int *inp;
  double range = 4.65661287308e-10;	/* 1.0 / 2147483648.0 */
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) bswap32(x)
#include "playmix_src.inc"
}

void
vmix_outmix_float_src (vmix_portc_t * portc, int nsamples)
{
  float *inp;
  double range = 1.0;
#undef BUFFER_TYPE
#define BUFFER_TYPE float *
#undef VMIX_BYTESWAP
#define VMIX_BYTESWAP(x) x
#include "playmix_src.inc"
}

static void
vmix_play_callback (int dev, int parm)
{
  int n;
  int voice;

  adev_t *adev = audio_engines[dev];
  dmap_t *dmap = adev->dmap_out;
  fp_flags_t fp_flags;
  unsigned int xyz;
  oss_native_word flags;
  int i;

  vmix_devc_t *devc = vmix_devc;
  vmix_mixer_t *mixer = devc->mixers[parm];
  vmix_engine_t *eng = &mixer->play_engine;

  fp_env_t fp_buf;
  short *fp_env = fp_buf;

  UP_STATUS (0x04);
  if (dmap == NULL || dmap->dmabuf == NULL)
    return;

  if (dmap->bytes_in_use == 0)
    {
      cmn_err (CE_WARN, "Bytes in use=0, eng=%d\n", adev->engine_num);
      return;
    }

  if (parm < 0 || parm >= devc->num_mixers)	/* Bad instance number */
    {
      cmn_err (CE_WARN, "Bad vmix instance %d\n", parm);
      return;
    }

  {
    /*
     * Align the FP save buffer to 16 byte boundary
     */
    oss_native_word p;
    p = (oss_native_word) fp_buf;

    p = ((p + 15ULL) / 16) * 16;
    fp_env = (short *) p;
  }

  MUTEX_ENTER_IRQDISABLE (mixer->mutex, flags);

  if (dmap->dmabuf == NULL)
    {
      MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags);
      return;
    }

  FP_SAVE (fp_env, fp_flags);

  n = 0;
  while (n++ < eng->max_playahead
	 && dmap_get_qlen (dmap) < eng->max_playahead)
    {
      int p;
      unsigned char *outbuf;

      for (i = 0; i < eng->channels; i++)
	memset (eng->chbufs[i], 0, CHBUF_SAMPLES * sizeof (vmix_sample_t));
      for (i = 0; i < mixer->num_clientdevs; i++)
	if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_OUTPUT)
	  {
	    vmix_portc_t *portc = mixer->client_portc[i];

	    if (portc->play_mixing_func == NULL)
	      continue;
	    if (portc->play_choffs + portc->channels >
		mixer->play_engine.channels)
	      portc->play_choffs = 0;
	    portc->play_mixing_func (portc,
				     mixer->play_engine.samples_per_frag);
	  }

      /* Export the output mix to the device */
      p = (int) (dmap->user_counter % dmap->bytes_in_use);
      outbuf = dmap->dmabuf + p;
      if (dmap->dmabuf != NULL)
	{
	  eng->converter (eng, outbuf, eng->chbufs, eng->channels,
			  eng->samples_per_frag);
	}

      dmap->user_counter += dmap->fragment_size;

    }

  FP_RESTORE (fp_env, fp_flags);
  MUTEX_EXIT_IRQRESTORE (mixer->mutex, flags);
/*
 * Call oss_audio_outputintr outside FP mode because it may 
 * cause a task switch (under Solaris). Task switch may turn on CR0.TS under
 * x86 which in turn will cause #nm exception.
 */
  for (i = 0; i < mixer->num_clientdevs; i++)
    if (mixer->client_portc[i]->trigger_bits & PCM_ENABLE_OUTPUT)
      {
	vmix_portc_t *portc = mixer->client_portc[i];
	oss_audio_outputintr (portc->audio_dev, 1);
      }

  for (i = 0; i < mixer->num_loopdevs; i++)
    {
      if (mixer->loop_portc[i]->trigger_bits & PCM_ENABLE_INPUT)
	{
	  oss_audio_inputintr (mixer->loop_portc[i]->audio_dev, 0);
	}
    }
  DOWN_STATUS (0x04);
}

void
vmix_setup_play_engine (vmix_mixer_t * mixer, adev_t * adev, dmap_t * dmap)
{
  int fmt;
  int frags = 0x7fff0007;	/* fragment size of 128 bytes */
  int i;
  int old_min;

/*
 * Sample format (and endianess) setup 
 *
 */

  // First make sure a sane format is selected before starting to probe
  fmt = adev->d->set_format (mixer->masterdev, AFMT_S16_LE);
  fmt = adev->d->set_format (mixer->masterdev, AFMT_S16_NE);

  // Find out the "best" sample format supported by the device

  if (adev->oformat_mask & AFMT_S16_NE)
    fmt = AFMT_S16_OE;
  if (adev->oformat_mask & AFMT_S16_NE)
    fmt = AFMT_S16_NE;

  if (vmix_multich_enable)	/* Better quality enabled */
    {
      if (adev->oformat_mask & AFMT_S32_NE)
	fmt = AFMT_S32_OE;
      if (adev->oformat_mask & AFMT_S32_NE)
	fmt = AFMT_S32_NE;
    }

  fmt = adev->d->set_format (mixer->masterdev, fmt);
  mixer->play_engine.fmt = fmt;

  switch (fmt)
    {
    case AFMT_S16_NE:
      mixer->play_engine.bits = 16;
      mixer->play_engine.converter = export16ne;
      break;

    case AFMT_S16_OE:
      mixer->play_engine.bits = 16;
      mixer->play_engine.converter = export16oe;
      break;

    case AFMT_S32_NE:
      mixer->play_engine.bits = 32;
      mixer->play_engine.converter = export32ne;
      break;

    case AFMT_S32_OE:
      mixer->play_engine.bits = 32;
      mixer->play_engine.converter = export32oe;
      break;

    default:
      cmn_err (CE_CONT, "Unrecognized sample format %x\n", fmt);
      return;
    }
/*
 * Number of channels
 */
  mixer->play_engine.channels = MAX_PLAY_CHANNELS;
  if (!vmix_multich_enable)
    mixer->play_engine.channels = 2;


  /* Force the device to stereo before trying with (possibly) imultiple channels */
  adev->d->set_channels (mixer->masterdev, 2);

  mixer->play_engine.channels = mixer->play_engine.channels =
    adev->d->set_channels (mixer->masterdev, mixer->play_engine.channels);

  if (mixer->play_engine.channels > MAX_PLAY_CHANNELS)
    {
      cmn_err (CE_WARN,
	       "Number of channels (%d) is larger than maximum (%d)\n",
	       mixer->play_engine.channels, MAX_PLAY_CHANNELS);
      return;
    }

  mixer->play_engine.rate =
    oss_audio_set_rate (mixer->masterdev, mixer->rate);

  if (mixer->play_engine.rate <= 22050)
    frags = 0x7fff0004;		/* Use smaller fragments */

  audio_engines[mixer->masterdev]->hw_parms.channels =
    mixer->play_engine.channels;
  audio_engines[mixer->masterdev]->hw_parms.rate = mixer->play_engine.rate;
  audio_engines[mixer->masterdev]->dmap_out->data_rate =
    mixer->play_engine.rate * mixer->play_engine.channels *
    mixer->play_engine.bits / 8;
  audio_engines[mixer->masterdev]->dmap_out->frame_size =
    mixer->play_engine.channels * mixer->play_engine.bits / 8;

  old_min = adev->min_fragments;
#if 0
  if ((adev->max_fragments == 0 || adev->max_fragments >= 4)
      && adev->min_block == 0)
    adev->min_fragments = 4;
#endif

  oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_SETFRAGMENT,
		   (ioctl_arg) & frags);
  oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_GETBLKSIZE,
		   (ioctl_arg) & mixer->play_engine.fragsize);

  dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags;

  oss_audio_ioctl (mixer->masterdev, NULL, SNDCTL_DSP_GETBLKSIZE,
		   (ioctl_arg) & mixer->play_engine.fragsize);
  adev->min_fragments = old_min;

  mixer->play_engine.fragsize = dmap->fragment_size;

  if (mixer->play_engine.channels > 2)
    {
      DDB (cmn_err
	   (CE_CONT, "Enabling multi channel play mode, %d hw channels\n",
	    mixer->play_engine.channels));
    }
  else if (mixer->play_engine.channels != 2)
    {
      cmn_err (CE_WARN,
	       "Master device doesn't support suitable channel configuration\n");

      return;
    }

/* 
 * Determine how many fragments we need to keep filled. 
 */
  if (adev->vmix_flags & VMIX_MULTIFRAG)
    mixer->play_engine.max_playahead = 32;
  else
    mixer->play_engine.max_playahead = 4;

  if (mixer->play_engine.max_playahead >
      audio_engines[mixer->masterdev]->dmap_out->nfrags)
    mixer->play_engine.max_playahead =
      audio_engines[mixer->masterdev]->dmap_out->nfrags;

/*
 * Try to keep one empty fragment after the one currently being played
 * by the device. Writing too close to the playback point may cause
 * massive clicking with some devices.
 */
  if (dmap->nfrags > 2 && mixer->play_engine.max_playahead == dmap->nfrags)
    mixer->play_engine.max_playahead--;

  mixer->play_engine.samples_per_frag =
    mixer->play_engine.fragsize / mixer->play_engine.channels /
    (mixer->play_engine.bits / 8);

  if (mixer->play_engine.samples_per_frag > CHBUF_SAMPLES)
    {
      cmn_err (CE_WARN, "Too many samples per fragment (%d,%d)\n",
	       mixer->play_engine.samples_per_frag, CHBUF_SAMPLES);
      return;
    }

  for (i = 0; i < mixer->play_engine.channels; i++)
    if (mixer->play_engine.chbufs[i] == NULL)	/* Not allocated yet */
      {
	mixer->play_engine.chbufs[i] =
	  PMALLOC (mixer->master_portc,
		   CHBUF_SAMPLES * sizeof (vmix_sample_t));
	if (mixer->play_engine.chbufs[i] == NULL)
	  {
	    cmn_err (CE_WARN, "Out of memory\n");
	    return;
	  }
      }

  dmap->audio_callback = vmix_play_callback;	/* Enable conversions */
  dmap->callback_parm = mixer->instance_num;
  dmap->dma_mode = PCM_ENABLE_OUTPUT;

  if (mixer->inputdev == mixer->masterdev)
    {
      mixer->record_engine.rate = mixer->play_engine.rate;
      mixer->record_engine.bits = mixer->play_engine.bits;
      mixer->record_engine.fragsize = mixer->play_engine.fragsize;
      mixer->record_engine.channels = mixer->play_engine.channels;
      mixer->record_engine.samples_per_frag =
	mixer->play_engine.samples_per_frag;
    }

  if (mixer->num_clientdevs > 1)
    {
      adev->redirect_out = mixer->client_portc[0]->audio_dev;
    }

  /*
   * Fill in the initial playback data (silence) to avoid underruns
   */
  vmix_play_callback (mixer->masterdev, mixer->instance_num);

  if (mixer->masterdev == mixer->inputdev)
    finalize_record_engine (mixer, fmt, audio_engines[mixer->inputdev],
			    audio_engines[mixer->inputdev]->dmap_in);
}
