/*
 * Purpose: Audio format conversion routines used by audio.c
 */

/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#undef  NO_COOKED_MODE
#define AUDIO_C

#define GRC	3

/* SINE_DEBUG enables internal test signal (sine wave) */
#undef  SINE_DEBUG

#include "oss_config.h"
#include "ulaw.h"
#define __int8_t_defined
#include "grc3.h"

/*
 * Audio format conversion stuff 
 */
#define CNV_NONE 	0x00000000
#define CNV_SRC		0x00000001

#define	CNV_CHNMASK	0x000000f0
#define CNV_M2S		0x00000010
#define CNV_S2M		0x00000020
#define CNV_MULT	0x00000040

#define CNV_Flog	0x00001000
#define CNV_F8bit	0x00002000
#define CNV_F16bit	0x00004000
#define CNV_F24bit	0x00008000
#define CNV_F32bit	0x00010000

#define CNV_Tlog	0x00100000
#define CNV_T8bit	0x00200000
#define CNV_T16bit	0x00400000
#define CNV_T24bit	0x00800000
#define CNV_T32bit	0x01000000

#define CNV_SIGN	0x10000000
#define CNV_ENDIAN	0x20000000
#define CNV_TOLOG	0x40000000
#define CNV_FROMLOG	0x80000000

static audio_format_info_t audio_format_info[] = {
  {"mu-Law", AFMT_MU_LAW, 0x80, 8, 0, ENDIAN_NONE, 1},
  {"A-Law", AFMT_A_LAW, 0x80, 8, 0, ENDIAN_NONE, 1},
  {"IMA-ADPCM", AFMT_IMA_ADPCM, 0x00, 4, 0, ENDIAN_NONE, 0, ALIGN_LSB, 1},
  {"8", AFMT_U8, 0x80, 8, 1, ENDIAN_NONE, 0},
  {"16(LE)", AFMT_S16_LE, 0x00, 16, 1, ENDIAN_LITTLE, 1},
  {"16(BE)", AFMT_S16_BE, 0x00, 16, 1, ENDIAN_BIG, 1},
  {"8(signed)", AFMT_S8, 0x00, 8, 1, ENDIAN_NONE, 1},
  {"16(unsigned LE)", AFMT_U16_LE, 0x00, 16, 1, ENDIAN_LITTLE, 0},
  {"16(unsigned BE)", AFMT_U16_BE, 0x00, 16, 1, ENDIAN_BIG, 0},
  {"mp2/mp3", AFMT_MPEG, 0x00, 8, 0, ENDIAN_NONE, 0, ALIGN_LSB, 1},
  {"AC3", AFMT_AC3, 0x00, 16, 0, ENDIAN_NONE, 0, ALIGN_LSB, 1},
  {"SPDIF (RAW)", AFMT_SPDIF_RAW, 0x00, 32, 0, ENDIAN_NONE, 0, ALIGN_LSB, 1},
  {"24(LE)", AFMT_S24_LE, 0x00, 32, 1, ENDIAN_LITTLE, 1, ALIGN_LSB},
  {"24(BE)", AFMT_S24_BE, 0x00, 32, 1, ENDIAN_BIG, 1, ALIGN_LSB},
  {"32(LE)", AFMT_S32_LE, 0x00, 32, 1, ENDIAN_LITTLE, 1},
  {"32(BE)", AFMT_S32_BE, 0x00, 32, 1, ENDIAN_BIG, 1},
  {"24(PACKED)", AFMT_S24_PACKED, 0x00, 24, 1, ENDIAN_LITTLE, 1, ALIGN_LSB},
  {0, 0}
};

audio_format_info_p
oss_find_format (unsigned int fmt)
{
  int i;

  i = 0;

  while (audio_format_info[i].fmt != 0)
    {
      if (audio_format_info[i].fmt == fmt)
	return &audio_format_info[i];
      i++;
    }

  return NULL;
}

#if 0
/*
 * Limiter stuff. Currently not in use.
 */
static const unsigned char limit8[] = {
/*-128*/ 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72,
/*-112*/ 72, 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, 80,
/* -96*/ 80, 81, 81, 82, 82, 83, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88,
/* -80*/ 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96,
/* -64*/ 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 103, 103,
  104,
/* -48*/ 104, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110,
  111, 111, 112,
/* -32*/ 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118,
  119, 119, 120,
/* -16*/ 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, 126, 126,
  127, 127, 128,
/*   0*/ 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, 134,
  134, 135, 135,
/*  16*/ 136, 136, 137, 137, 138, 138, 139, 139, 140, 140, 141, 141, 142,
  142, 143, 143,
/*  32*/ 144, 144, 145, 145, 146, 146, 147, 147, 148, 148, 149, 149, 150,
  150, 151, 151,
/*  48*/ 152, 152, 153, 153, 154, 154, 155, 155, 156, 156, 157, 157, 158,
  158, 159, 159,
/*  64*/ 160, 160, 161, 161, 162, 162, 163, 163, 164, 164, 165, 165, 166,
  166, 167, 167,
/*  80*/ 168, 168, 169, 169, 170, 170, 171, 171, 172, 172, 173, 173, 174,
  174, 175, 175,
/*  96*/ 176, 176, 177, 177, 178, 178, 179, 179, 180, 180, 181, 181, 182,
  182, 183, 183,
/* 112*/ 184, 184, 185, 185, 186, 186, 187, 187, 188, 188, 189, 189, 190,
  190, 191, 191
};

static __inline__ void
limit_8bit (unsigned char *buf, int l)
{
  int i;

  for (i = 0; i < l; i++)
    buf[i] = limit8[buf[i]];
}

static __inline__ void
limit_16bit (short *buf, int l)
{
  int i;
  l /= sizeof (*buf);

  for (i = 0; i < l; i++)
    buf[i] /= 2;
}

static __inline__ void
limit_32bit (int *buf, int l)
{
  int i;
  l /= sizeof (*buf);

  for (i = 0; i < l; i++)
    buf[i] /= 2;
}

static const unsigned char unlimit8[] = {
/*   0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*  16*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*  32*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*  48*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/*  64*/ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
/*  80*/ 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
/*  96*/ 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94,
/* 112*/ 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122,
  124, 126,
/* 128*/ 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152,
  154, 156, 158,
/* 144*/ 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184,
  186, 188, 190,
/* 160*/ 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216,
  218, 220, 222,
/* 176*/ 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248,
  250, 252, 254,
/* 192*/ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255,
/* 208*/ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255,
/* 224*/ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255,
/* 240*/ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255
};

static __inline__ void
unlimit_8bit (unsigned char *buf, int l)
{
  int i;

  for (i = 0; i < l; i++)
    buf[i] = unlimit8[buf[i]];
}

static __inline__ void
unlimit_16bit (short *buf, int l)
{
  int i;
  l /= sizeof (*buf);

  for (i = 0; i < l; i++)
    buf[i] *= 2;
}

static __inline__ void
unlimit_32bit (int *buf, int l)
{
  int i;
  l /= sizeof (*buf);

  for (i = 0; i < l; i++)
    buf[i] *= 2;
}
#endif

/*ARGSUSED*/
static int
do_src (adev_p adev, dmap_p dmap, int srcrate, int tgtrate, void *p1,
	void *p2, int *len, int channels, int bits, int ssize)
{
#if GRC == 2
  int err, l, l1, l2;

  l = *len;

  err = src_convert (dmap->src, p1, l, bits, p2, 4096, 24, &l1, &l2, 0);
  *len = l2;
  if (l1 != l)
    cmn_err (CE_NOTE, "Everything not taken %d/%d\n", l1, l);

#endif

#if GRC == 3
  int ch, l, size, nsamples, err;

  l = *len / ssize;
  size = TMP_CONVERT_BUF_SIZE / ssize;
  size /= channels;
  nsamples = l / channels;

  if (bits == 24)
    bits = 32;

#  ifdef DO_TIMINGS
  {
    char tmp[128];
    sprintf (tmp, "grc3_convert %d bytes / %d samples * %d channels in. ",
	     *len, nsamples, channels);
    oss_do_timing (tmp);
  }
#  endif

#if 0
  /* Limit the volume levels before calling GRC3 */
  buf = p1;

  switch (ssize)
    {
    case 1:
      limit_8bit ((unsigned char *) buf, *len);
      break;
    case 2:
      limit_16bit ((short *) buf, *len);
      break;
    case 4:
      limit_32bit ((int *) buf, *len);
      break;
    }
#endif

  for (ch = 0; ch < channels; ch++)
    {
      if ((err = grc3_convert (dmap->srcstate[ch], bits, adev->src_quality,	/* Numbits, quality */
			       p1, p2, nsamples, size, channels, ch)) < 0)
	{
	  cmn_err (CE_WARN, "SRC failed (%d), bits=%d, ssize=%d\n", err, bits,
		   ssize);
	  return -EIO;
	}

      *len = ((grc3state_t *) dmap->srcstate[ch])->outsz * ssize * channels;
      VMEM_CHECK (p2, *len);
    }
#if 0
  buf = p2;
  switch (ssize)
    {
    case 1:
      unlimit_8bit ((unsigned char *) buf, *len);
      break;
    case 2:
      unlimit_16bit ((short *) buf, *len);
      break;
    case 4:
      unlimit_32bit ((int *) buf, *len);
      break;
    }
#endif
#  ifdef DO_TIMINGS
  {
    char tmp[128];
    sprintf (tmp, "-> %d bytes, %d samples out, %d samples in",
	     *len, ((grc3state_t *) dmap->srcstate[0])->outsz,
	     ((grc3state_t *) dmap->srcstate[0])->insz);
    oss_do_timing (tmp);
  }
#  endif
#endif

  return 0;
}

static int
setup_src (adev_p adev, dmap_p dmap, int srate, int trate, int sch, int tch)
{
  int ch, nch;

  nch = sch;
  if (tch < nch)
    nch = tch;

  if (adev->src_quality < 1)
    adev->src_quality = 1;
  if (adev->src_quality > 5)
    adev->src_quality = 5;

#if GRC == 2
  if (nch > 2)
    {
      cmn_err (CE_WARN, "Too many channels for SRC (%d)\n", nch);
      return -EIO;
    }
  {
    int val;
    val = src_find_output_rate (trate, srate);
    if (src_open (&dmap->src, srate, trate, nch, 0) != 0)
      {
	cmn_err (CE_CONT, "OSS audio: SRC open failed\n");
	return -EIO;
      }
  }
#endif
#if GRC == 3
  if (nch > OSS_MAX_CONVERT_CHANNELS)
    {
      cmn_err (CE_WARN, "Too many channels for SRC (%d)\n", nch);
      return -EIO;
    }
#ifdef DO_TIMINGS
  {
    char tmp[128];
    sprintf (tmp, "grc3_setup %d -> %d Hz, %d channels", srate, trate, nch);
    oss_do_timing (tmp);
  }
#endif

  for (ch = 0; ch < nch; ch++)
    {
      if (dmap->srcstate[ch] == NULL)
	{
	  dmap->srcstate[ch] = PMALLOC (dmap->osdev, sizeof (grc3state_t));
	}
      grc3_reset (dmap->srcstate[ch]);
      grc3_setup (dmap->srcstate[ch], srate, trate);
    }
#endif
  return 0;
}

#if !defined(__OpenBSD__)
static __inline__ unsigned int
swap32 (unsigned int x)
{

#if 1
  unsigned int y = 0;
  unsigned char *a = ((unsigned char *) &x) + 3;
  unsigned char *b = (unsigned char *) &y;

  *b++ = *a--;
  *b++ = *a--;
  *b++ = *a--;
  *b++ = *a--;

  return y;
#else
  /* Old version */
  return ((x & 0x000000ff) << 24) |
    ((x & 0x0000ff00) << 8) |
    ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24);
#endif
}
#endif

#ifdef SINE_DEBUG
#define SIN_STEPS	48
static short sinebuf[48] = {
  0, 4276, 8480, 12539, 16383, 19947, 23169, 25995,
  28377, 30272, 31650, 32486, 32767, 32486, 31650, 30272,
  28377, 25995, 23169, 19947, 16383, 12539, 8480, 4276,
  0, -4276, -8480, -12539, -16383, -19947, -23169, -25995,
  -28377, -30272, -31650, -32486, -32767, -32486, -31650, -30272,
  -28377, -25995, -23169, -19947, -16383, -12539, -8480, -4276
};
static int sine_ptr = 0;
#endif

static int
cnv_default (adev_p adev, dmap_p dmap, void **srcp, int *srcl, void **tgtp,
	     sample_parms * source, sample_parms * target)
{
  void *p1 = *srcp, *p2 = *tgtp, *tmpp;
  int len = *srcl, l, i;

/*
 * Convert samples to 24 bit (32 bit lsb aligned) if necessary.
 */
  VMEM_CHECK (p1, len);
  VMEM_CHECK (p2, len);
  if (source->fmt != AFMT_S24_NE)
    switch (source->fmt)
      {
      case AFMT_U8:
	{
	  unsigned char *fromp = p1;
	  unsigned int *top = p2;

	  l = len;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      *top++ = ((signed char) (*fromp++ ^ 0x80)) << 16;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S8:
	{
	  char *fromp = p1;
	  int *top = p2;
	  int t;

	  l = len;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      t = (*fromp++) << 24;
	      *top++ = t >> 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_MU_LAW:
	{
	  unsigned char *fromp = p1;
	  unsigned int *top = p2;

	  l = len;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      *top++ = ((signed char) (ulaw_dsp[(*fromp++)] ^ 0x80)) << 16;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S16_NE:
	{
	  short *fromp = p1;
	  int *top = p2;

	  l = len / 2;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++ << 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S16_OE:
	{
	  short *fromp = p1;
	  int *top = p2;

	  l = len / 2;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      short tmp;
	      int t;
	      tmp = *fromp++;
	      /* Try to maintain the sign bit by shifting 8 bits too far */
	      t = ((tmp & 0xff) << 24) | ((tmp & 0xff00) << 8);
	      *top++ = t >> 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S32_NE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++ >> 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S32_OE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = swap32 (*fromp++) >> 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S24_OE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = swap32 ((unsigned int) (*fromp++));
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S24_PACKED:
	{
	  unsigned char *fromp = p1;
	  int *top = p2;

	  l = len / 3;
	  len = l * sizeof (int);

	  for (i = 0; i < l; i++)
	    {
	      int tmp = 0;

#if 1
	      tmp |= (*fromp++);
	      tmp |= (*fromp++) << 8;
	      tmp |= (*fromp++) << 16;
#else
	      tmp |= (*fromp++) << 16;
	      tmp |= (*fromp++) << 8;
	      tmp |= (*fromp++);
#endif

	      *top++ = tmp;

	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      default:
	cmn_err (CE_WARN, "Unsupported conversion source format %08x\n",
		 source->fmt);
	return -EIO;
      }

  if (source->rate != target->rate && source->channels <= target->channels)
    {
      int err;

      VMEM_CHECK (p1, len);
      err =
	do_src (adev, dmap, source->rate, target->rate, p1, p2, &len,
		source->channels, 32, 4);
      if (err < 0)
	return err;

      tmpp = p1;
      p1 = p2;
      p2 = tmpp;
    }

/*
 * Convert between mono and stereo
 */

  if (source->channels != target->channels)
    {
      VMEM_CHECK (p1, len);
      if (source->channels == 1 && target->channels == 2)
	{
	  /* Mono -> Stereo */
	  int *fromp = p1, *top = p2;

	  l = len / sizeof (int);	/* Number of (mono) samples */
	  len = len * 2;	/* Number of bytes will get doubled */
	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp;
	      *top++ = *fromp++;
	    }
	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
      else if (source->channels == 2 && target->channels == 1)
	{
	  /* Stereo -> mono */
	  int *fromp = p1, *top = p2;

	  len = len / 2;	/* Number of bytes will drop to a half */
	  l = len / sizeof (int);	/* Number of (mono) samples */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++;	/* Take just the left channel sample */
	      fromp++;		/* discard the right channel one */
	    }
	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
      else
	{
	  /* Multi channel conversions */

	  int *fromp = p1, *top = p2;
	  int n, nc, sc, tc;

	  l = len / sizeof (int);
	  n = l / source->channels;
	  len = len * target->channels / source->channels;

	  tc = target->channels;
	  sc = source->channels;
	  nc = tc;
	  if (nc > sc)
	    nc = sc;

	  memset (top, 0, len);

	  for (i = 0; i < n; i++)
	    {
	      int c;

	      for (c = 0; c < nc; c++)
		top[c] = fromp[c];

	      fromp += sc;
	      top += tc;
	    }
	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
    }

  if (source->rate != target->rate && source->channels > target->channels)
    {
      int err;

      VMEM_CHECK (p1, len);
      err =
	do_src (adev, dmap, source->rate, target->rate, p1, p2, &len,
		target->channels, 24, 4);
      if (err < 0)
	return err;

      tmpp = p1;
      p1 = p2;
      p2 = tmpp;
    }

#ifdef SINE_DEBUG
/*
 * Debugging stuff. Overwrite the sample buffer with pure sine wave.
 */
  {
    int *p = p1;
    l = len / sizeof (int);
    for (i = 0; i < l; i += 2)
      {
#if 1
	*p++ = sinebuf[sine_ptr] << 15;	/* Left chn */
	*p++ = sinebuf[sine_ptr] << 15;	/* Right chn */
	sine_ptr = (sine_ptr + 1) % SIN_STEPS;
#else
	static short pp = 0;
	*p++ = pp * 256;
	*p++ = pp * 256;
	pp++;
#endif
      }
  }
#endif

/*
 * Finally convert the samples from internal 24 bit format to the target format
 */

  if (target->fmt != AFMT_S24_NE)
    switch (target->fmt)
      {
      case AFMT_U8:
	{
	  unsigned int *fromp = p1;
	  unsigned char *top = p2;

	  l = len / sizeof (int);
	  len = l;

	  for (i = 0; i < l; i++)
	    {
	      *top++ = (*fromp++ >> 16) ^ 0x80;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S8:
	{
	  int *fromp = p1;
	  char *top = p2;

	  l = len / sizeof (int);
	  len = l;

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++ >> 16;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S16_NE:
	{
	  int *fromp = p1;
	  short *top = p2;

	  l = len / sizeof (int);
	  len = l * sizeof (short);

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++ >> 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S16_OE:
	{
	  int *fromp = p1;
	  unsigned char *top = p2;

	  l = len / sizeof (int);
	  len = l * sizeof (short);

	  for (i = 0; i < l; i++)
	    {
	      int tmp;
	      tmp = *fromp++ >> 8;
	      *top++ = tmp & 0xff;
	      *top++ = (tmp >> 8) & 0xff;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S32_NE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = *fromp++ << 8;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S32_OE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = swap32 ((unsigned int) (*fromp++ << 8));
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S24_OE:
	{
	  int *fromp = p1;
	  int *top = p2;

	  l = len / 4;
	  /* len = l * sizeof (int); */

	  for (i = 0; i < l; i++)
	    {
	      *top++ = swap32 ((unsigned int) (*fromp++));
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_S24_PACKED:
	{
	  int *fromp = p1;
	  unsigned char *top = p2;

	  l = len / 4;
	  len = l * 3;

	  for (i = 0; i < l; i++)
	    {
	      int tmp = *fromp++;
	      *top++ = tmp & 0xff;
	      *top++ = (tmp >> 8) & 0xff;
	      *top++ = (tmp >> 16) & 0xff;
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      case AFMT_MU_LAW:
	{
	  unsigned int *fromp = p1;
	  unsigned char *top = p2;

	  l = len / sizeof (int);
	  len = l;

	  for (i = 0; i < l; i++)
	    {
	      *top++ = dsp_ulaw[((*fromp++ >> 16) & 0xff) ^ 0x80];
	    }

	  tmpp = p1;
	  p1 = p2;
	  p2 = tmpp;
	}
	break;

      default:
	cmn_err (CE_WARN, "Unsupported conversion target format %08x\n",
		 target->fmt);
	return -EIO;
      }

  VMEM_CHECK (p1, len);
  *srcl = len;
  *srcp = p1;
  *tgtp = p2;

  return 0;
}

static int
cnv_srconly (adev_p adev, dmap_p dmap, void **srcp, int *srcl, void **tgtp,
	     sample_parms * source, sample_parms * target)
{
  void *p1 = *srcp, *p2 = *tgtp, *tmpp;
  int len = *srcl;
  int err;
  int ssize = 2;
  audio_format_info_p fmt;

  VMEM_CHECK (p1, len);
  VMEM_CHECK (p2, len);

  if ((fmt = oss_find_format (source->fmt)) == NULL)
    {
      cmn_err (CE_WARN, "Unknown audio format %x\n", source->fmt);
      return -EIO;
    }

  switch (fmt->bits)
    {
    default:
    case 8:
      ssize = 1;
      break;
    case 16:
      ssize = 2;
      break;
    case 24:
      ssize = 4;
      break;
    case 32:
      ssize = 4;
      break;
    }

  err =
    do_src (adev, dmap, source->rate, target->rate, p1, p2, &len,
	    source->channels, fmt->bits, ssize);
  if (err < 0)
    return err;

  tmpp = p1;
  p1 = p2;
  p2 = tmpp;

  VMEM_CHECK (p1, len);
  *srcl = len;
  *srcp = p1;
  *tgtp = p2;

  return 0;
}

#include "audiocnv.inc"

static cnv_func_t
select_converter (unsigned int cnv, int expand, audio_format_info_p src_f,
		  audio_format_info_p tgt_f)
{
#if 1
  {
    int expand2 = expand * 100 / UNIT_EXPAND;
    DDB (cmn_err
	 (CE_CONT, "Expand = %d (%d.%02d)\n", expand, expand2 / 100,
	  expand2 % 100));
    DDB (cmn_err (CE_CONT, "Convert = %08x / ", cnv));
    if (cnv & CNV_SRC)
      DDB (cmn_err (CE_CONT, "CNV_SRC "));
    if (cnv & CNV_M2S)
      DDB (cmn_err (CE_CONT, "CNV_M2S "));
    if (cnv & CNV_S2M)
      DDB (cmn_err (CE_CONT, "CNV_S2M "));
    if (cnv & CNV_MULT)
      DDB (cmn_err (CE_CONT, "CNV_MULT "));
    if (cnv & CNV_Flog)
      DDB (cmn_err (CE_CONT, "CNV_Flog "));
    if (cnv & CNV_F8bit)
      DDB (cmn_err (CE_CONT, "CNV_F8bit "));
    if (cnv & CNV_F16bit)
      DDB (cmn_err (CE_CONT, "CNV_F16bit "));
    if (cnv & CNV_F24bit)
      DDB (cmn_err (CE_CONT, "CNV_F24bit "));
    if (cnv & CNV_F32bit)
      DDB (cmn_err (CE_CONT, "CNV_F32bit "));
    if (cnv & CNV_Tlog)
      DDB (cmn_err (CE_CONT, "CNV_Tlog "));
    if (cnv & CNV_T8bit)
      DDB (cmn_err (CE_CONT, "CNV_T8bit "));
    if (cnv & CNV_T16bit)
      DDB (cmn_err (CE_CONT, "CNV_T16bit "));
    if (cnv & CNV_T24bit)
      DDB (cmn_err (CE_CONT, "CNV_T24bit "));
    if (cnv & CNV_T32bit)
      DDB (cmn_err (CE_CONT, "CNV_T32bit "));
    if (cnv & CNV_SIGN)
      DDB (cmn_err (CE_CONT, "CNV_SIGN "));
    if (cnv & CNV_ENDIAN)
      DDB (cmn_err (CE_CONT, "CNV_ENDIAN "));
    if (cnv & CNV_TOLOG)
      DDB (cmn_err (CE_CONT, "CNV_TOLOG "));
    if (cnv & CNV_FROMLOG)
      DDB (cmn_err (CE_CONT, "CNV_FROMLOG "));
    DDB (cmn_err (CE_CONT, "\n"));
  }
  DDB (cmn_err (CE_CONT, "\n"));
#endif

  if (cnv == CNV_SRC)		/* Only SRC is needed */
    if (src_f->fmt & (CONVERTABLE_FORMATS & ~AFMT_MU_LAW))	/* Can convert this */
#ifdef OSS_BIG_ENDIAN
      if (src_f->endianess == ENDIAN_BIG)
#else
      if (src_f->endianess == ENDIAN_LITTLE)
#endif
	{
	  DDB (cmn_err (CE_CONT, "Only SRC\n"));
	  return cnv_srconly;
	}

  if (cnv & CNV_SRC)
    {				/* Needs SRC together with some other conversion. Use the default. */
      return cnv_default;
    }

  if (src_f->endianess != tgt_f->endianess)
    {				/* Needs endianess handling - use the default for the time being. */
      return cnv_default;
    }

#ifdef OSS_BIG_ENDIAN
  if (src_f->endianess != ENDIAN_BIG)
#else
  if (src_f->endianess != ENDIAN_LITTLE)
#endif
    {				/* Opposite endianess - use the default. */
      return cnv_default;
    }

/*
 * The remaining conversions don't use SRC  and the endianess is native 
 * so they are easier to optimize.
 */

  switch (cnv)
    {
    case CNV_F8bit | CNV_T16bit:
      return cnv_F8bits_T16bits;
    case CNV_F8bit | CNV_T32bit:
      return cnv_F8bits_T32bits;
    case CNV_F16bit | CNV_T32bit:
      return cnv_F16bits_T32bits;
    case CNV_F32bit | CNV_T16bit:
      return cnv_F32bits_T16bits;
    case CNV_F32bit | CNV_T8bit:
      return cnv_F32bits_T8bits;
    case CNV_F16bit | CNV_T8bit:
      return cnv_F16bits_T8bits;
    }

  DDB (cmn_err (CE_CONT, "Will use the default converter\n"));
  return cnv_default;
}

/*ARGSUSED*/
int
setup_format_conversions (adev_p adev, dmap_p dmap, sample_parms * source,
			  sample_parms * target,
			  sample_parms * user,
			  sample_parms * device, int format_mask)
{
  int expand = UNIT_EXPAND;
  unsigned int cnv = CNV_NONE;
  audio_format_info_p src_f, tgt_f;

  dmap->expand_factor = UNIT_EXPAND;

  if (source->fmt == AFMT_AC3)
    {
      source->channels = target->channels = 2;
      source->rate = target->rate;
      target->fmt = source->fmt;
      dmap->convert_mode = 0;
      dmap->convert_func = NULL;
      return 0;
    }

  if ((src_f = oss_find_format (source->fmt)) == NULL)
    {
      cmn_err (CE_CONT, "internal format error 1 (%x)\n", source->fmt);
      return -EIO;
    }

  if ((tgt_f = oss_find_format (target->fmt)) == NULL)
    {
      cmn_err (CE_CONT, "internal format error 2\n");
      return -EIO;
    }

#ifdef DO_TIMINGS
  {
    char tmp[128];

    sprintf (tmp, "Setting up format conversios for device %d",
	     adev->engine_num);
    oss_do_timing (tmp);
    sprintf (tmp, "  Speed %d->%d", source->rate, target->rate);
    oss_do_timing (tmp);
    sprintf (tmp, "  Channels %d->%d", source->channels, target->channels);
    oss_do_timing (tmp);
    sprintf (tmp, "  Format %02x/%s->%02x/%s", source->fmt, src_f->name,
	     target->fmt, tgt_f->name);
    oss_do_timing (tmp);
  }
#endif
  DDB (cmn_err
       (CE_CONT, "Setting up format conversios for device %d\n",
	adev->engine_num));
  DDB (cmn_err (CE_CONT, "  Speed %d->%d\n", source->rate, target->rate));
  DDB (cmn_err
       (CE_CONT, "  Channels %d->%d\n", source->channels, target->channels));
  DDB (cmn_err
       (CE_CONT, "  Format %02x/%s->%02x/%s\n", source->fmt, src_f->name,
	target->fmt, tgt_f->name));

  if (source->channels > OSS_MAX_CONVERT_CHANNELS
      || target->channels > OSS_MAX_CONVERT_CHANNELS)
    {
      cmn_err
	(CE_WARN,
	 "Audio format conversions not supported with more than %d channels\n",
	 OSS_MAX_CONVERT_CHANNELS);
      dmap->flags &= ~DMAP_COOKED;
      return -EIO;
    }

  expand = (expand * target->rate) / source->rate;
  expand = (expand * target->channels) / source->channels;
  expand = (expand * tgt_f->bits) / src_f->bits;

  dmap->expand_factor = expand;

  if (source->rate != target->rate)
    cnv |= CNV_SRC;

  if (source->channels != target->channels)	/* Change # of channels */
    {
      if (target->channels > 2 || source->channels > 2)
	cnv |= CNV_MULT;
      else
	{
	  if (source->channels == 1 && target->channels == 2)
	    cnv |= CNV_M2S;
	  else
	    cnv |= CNV_S2M;
	}
    }

  if (source->fmt != target->fmt)
    {

      if (src_f->endianess != tgt_f->endianess)
	if (src_f->endianess != ENDIAN_NONE
	    && tgt_f->endianess != ENDIAN_NONE)
	  {			/* Endianess change */
	    cnv |= CNV_ENDIAN;
	  }

      if (src_f->endianess != ENDIAN_NONE && tgt_f->endianess != ENDIAN_NONE)
	if (src_f->is_signed != tgt_f->is_signed)
	  {
	    cnv |= CNV_SIGN;
	  }

      if (src_f->bits != tgt_f->bits)
	{
	  switch (src_f->bits)
	    {
	    case 8:
	      cnv |= CNV_F8bit;
	      break;
	    case 16:
	      cnv |= CNV_F16bit;
	      break;
	    case 24:
	      cnv |= CNV_F24bit;
	      break;
	    case 32:
	      cnv |= CNV_F32bit;
	      break;
	    default:
	      cnv |= CNV_Flog;
	    }

	  switch (tgt_f->bits)
	    {
	    case 8:
	      cnv |= CNV_T8bit;
	      break;
	    case 16:
	      cnv |= CNV_T16bit;
	      break;
	    case 24:
	      cnv |= CNV_T24bit;
	      break;
	    case 32:
	      cnv |= CNV_T32bit;
	      break;
	    default:
	      cnv |= CNV_Tlog;
	    }
	}

      if (!src_f->is_linear)
	cnv |= CNV_FROMLOG;

      if (!tgt_f->is_linear)
	cnv |= CNV_TOLOG;
    }

  dmap->convert_mode = cnv;

  dmap->convert_func = select_converter (cnv, expand, src_f, tgt_f);

  dmap->tmpbuf_len = dmap->tmpbuf_ptr = 0;
  if (dmap->tmpbuf1 == NULL)
    {
      dmap->tmpbuf1 = PMALLOC (dmap->osdev, TMP_CONVERT_BUF_SIZE);
    }

  if (dmap->tmpbuf2 == NULL)
    {
      dmap->tmpbuf2 = PMALLOC (dmap->osdev, TMP_CONVERT_BUF_SIZE);
    }

  if (dmap->tmpbuf1 == NULL || dmap->tmpbuf2 == NULL)
    return -ENOSPC;

  if (cnv & CNV_SRC)
    if (setup_src
	(adev, dmap, source->rate, target->rate, source->channels,
	 target->channels) < 0)
      {
	cmn_err (CE_CONT, "internal format error 3\n");
	return -EIO;
      }

  return 0;
}
