/* 
 * pvfau.c
 *
 * Conversion pvf <--> au.
 *
 */
 
char pvfau_c[] = "$Id: pvfau.c,v 1.8 1995/03/29 18:27:15 marc Exp $";

#include <stdio.h>

#ifndef _NOSTDLIB_H
#include <stdlib.h>
#endif

#include "../mgetty.h"
#include "pvflib.h"

typedef long Word; /* must be 32 bits */

typedef struct {
    Word magic;               /* magic number SND_MAGIC */
    Word dataLocation;        /* offset or pointer to the data */
    Word dataSize;            /* number of bytes of data */
    Word dataFormat;          /* the data format code */
    Word samplingRate;        /* the sampling rate */
    Word channelCount;        /* the number of channels */
    Word info;                /* optional text information */
} SNDSoundStruct;

#define SND_FORMAT_MULAW_8    1
#define SND_FORMAT_LINEAR_8   2
#define SND_FORMAT_LINEAR_16  3

#define SND_MAGIC (0x2e736e64L)
#define SND_HEADER_SIZE       28
#define SND_UNKNOWN_SIZE      ((Word)(-1))

static char sound_format[][30]={
    "unspecified",
    "uLaw_8",
    "linear_8",
    "linear_16",
    "linear_24",
    "linear_32",
    "float",
    "double",
    "fragmented",
};

/*
** This routine converts from linear to ulaw.
**
** Craig Reese: IDA/Supercomputing Research Center
** Joe Campbell: Department of Defense
** 29 September 1989
**
** References:
** 1) CCITT Recommendation G.711  (very difficult to follow)
** 2) "A New Digital Technique for Implementation of Any
**     Continuous PCM Companding Law," Villeret, Michel,
**     et al. 1973 IEEE Int. Conf. on Communications, Vol 1,
**     1973, pg. 11.12-11.17
** 3) MIL-STD-188-113,"Interoperability and Performance Standards
**     for Analog-to_Digital Conversion Techniques,"
**     17 February 1987
**
** Input: Signed 16 bit linear sample
** Output: 8 bit ulaw sample
*/

#define ZEROTRAP    /* turn on the trap as per the MIL-STD */
#undef ZEROTRAP
#define BIAS 0x84   /* define the add-in bias for 16 bit samples */
#define CLIP 32635

static unsigned char
linear2ulaw _P1 ((sample), int sample) {
  static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
                             4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
                             5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
                             5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                             6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                             7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
  int sign, exponent, mantissa;
  unsigned char ulawbyte;

  /* Get the sample into sign-magnitude. */
  sign = (sample >> 8) & 0x80;		/* set aside the sign */
  if(sign != 0) sample = -sample;		/* get magnitude */
  if(sample > CLIP) sample = CLIP;		/* clip the magnitude */

  /* Convert from 16 bit linear to ulaw. */
  sample = sample + BIAS;
  exponent = exp_lut[( sample >> 7 ) & 0xFF];
  mantissa = (sample >> (exponent + 3)) & 0x0F;
  ulawbyte = ~(sign | (exponent << 4) | mantissa);
#ifdef ZEROTRAP
  if (ulawbyte == 0) ulawbyte = 0x02;	/* optional CCITT trap */
#endif

  return(ulawbyte);
}

/*
** This routine converts from ulaw to 16 bit linear.
**
** Craig Reese: IDA/Supercomputing Research Center
** 29 September 1989
**
** References:
** 1) CCITT Recommendation G.711  (very difficult to follow)
** 2) MIL-STD-188-113,"Interoperability and Performance Standards
**     for Analog-to_Digital Conversion Techniques,"
**     17 February 1987
**
** Input: 8 bit ulaw sample
** Output: signed 16 bit linear sample
*/

static int
ulaw2linear _P1((ulawbyte), unsigned char ulawbyte) {
  static int exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
  int sign, exponent, mantissa, sample;

  ulawbyte = ~ulawbyte;
  sign = (ulawbyte & 0x80);
  exponent = (ulawbyte >> 4) & 0x07;
  mantissa = ulawbyte & 0x0F;
  sample = exp_lut[exponent] + (mantissa << (exponent + 3));
  if(sign != 0) sample = -sample;

  return(sample);
}

Word
read_word _P1((in), FILE *in) {
    Word w;
    w=         getc(in);
    w=(w<<8) | getc(in);
    w=(w<<8) | getc(in);
    w=(w<<8) | getc(in);
    return w;
}

void
write_word _P2((w, out), Word w, FILE *out) {
    putc((w&0xff000000) >>24, out);
    putc((w&0x00ff0000) >>16, out);
    putc((w&0x0000ff00) >> 8, out);
    putc((w&0x000000ff)     , out);
}

int
pvftoau _P2((argc, argv), int argc, char **argv )
{
    FILE *in=stdin, *out=stdout;
    
    SNDSoundStruct hdr;
    hdr.dataFormat = SND_FORMAT_LINEAR_16;
    
    if (argc > 1 && !strcmp(argv[1], "-ulaw"))
    {
	hdr.dataFormat = SND_FORMAT_MULAW_8;
	--argc, argv++;
    }
    if (argc > 1 && !strcmp (argv[1], "-linear8"))
    {
	hdr.dataFormat = SND_FORMAT_LINEAR_8;
	--argc, argv++;
    }
    
    if (argc > 1 && (hdr.samplingRate = atoi (argv[1])))
	--argc, argv++;
    else
	hdr.samplingRate = RATE;
    
    if( argc>1 ) USAGE("[-ulaw|-linear8] [rate]");

    hdr.magic = SND_MAGIC;
    hdr.dataLocation = SND_HEADER_SIZE;
    hdr.dataSize = SND_UNKNOWN_SIZE;
    hdr.channelCount = 1;
    hdr.info = 0;
    
    write_word(hdr.magic, out);
    write_word(hdr.dataLocation, out);
    write_word(hdr.dataSize, out);
    write_word(hdr.dataFormat, out);
    write_word(hdr.samplingRate, out);
    write_word(hdr.channelCount, out);
    write_word(hdr.info, out);
    
    switch (hdr.dataFormat)
    {
      case SND_FORMAT_MULAW_8:
	while(1) {
	    int sample;
	    sample = zget(in);
	    if(feof(in)) break;
	    putc(linear2ulaw (sample), out);
	}
	break;
      case SND_FORMAT_LINEAR_8:
	while(1) {
	    int sample;
	    sample = zget(in);
	    if(feof(in)) break;
	    putc(sample >> 8, out);
	}
	break;
      case SND_FORMAT_LINEAR_16:
	while(1) {
	    int sample;
	    sample = zget(in);
	    if(feof(in)) break;
	    zput(sample, out);
	}
	break;
      default:
	ERRORRETURN("Unsupported sound file format requested");
    }
    return 0;
}

int
autopvf _P2((argc, argv), int argc, char **argv )
{
    FILE *in=stdin, *out=stdout;
    int i;
    
    SNDSoundStruct hdr;
    
    if( argc>2 ) USAGE("[-info]");
    
    hdr.magic = read_word(in);
    hdr.dataLocation = read_word(in);
    hdr.dataSize = read_word(in);
    hdr.dataFormat = read_word(in);
    hdr.samplingRate = read_word(in);
    hdr.channelCount = read_word(in);
    /* hdr.info = read_word(in); */ /* this is sometimes missing */
    
    if(hdr.magic != SND_MAGIC)
	ERRORRETURN("illegal magic number for an .au file");
    
    if (argc > 1 && !strcmp(argv[1], "-info"))
    {
	Word fmt=hdr.dataFormat;
	if(fmt>=0 && fmt <
	   (sizeof(sound_format)/sizeof(sound_format[0]))) {
	    fprintf(stderr, "Data format: %s\n",
		    sound_format[fmt]);
	} else {
	    fprintf(stderr, "Data format unknown, code=%ld\n", (long)fmt);
	}
	fprintf(stderr, "Sampling rate: %ld\n", (long) hdr.samplingRate);
	fprintf(stderr, "Number of channels: %ld\n", (long) hdr.channelCount);
	fprintf(stderr, "Data location: %ld\n", (long) hdr.dataLocation);
	fprintf(stderr, "Data size: %ld\n", (long) hdr.dataSize);
	--argc, argv++;
    }
    
    if (hdr.channelCount != 1)
	ERRORRETURN("number of channels != 1 (only mono supported)");
    
    for (i=SND_HEADER_SIZE; i<hdr.dataLocation; i++)
	if (getc(in) == EOF)
	    ERRORRETURN("unexpected EOF");
    
    switch (hdr.dataFormat)
    {
      case SND_FORMAT_MULAW_8:
	while(1) {
	    int c;
	    c=getc(in);
	    if(feof(in)) break;
	    zput (ulaw2linear(c), out);
	}
	break;
      case SND_FORMAT_LINEAR_8:
	while(1) {
	    int c;
	    c= (getc(in) & 0xff);
	    if (c >= 0x80)	/* sign extend */
		c -= 0x100;
	    if(feof(in)) break;
	    zput(c<<8, out);
	}
	break;
      case SND_FORMAT_LINEAR_16:
	while(1) {
	    int c;
	    c=zget(in);
	    if(feof(in)) break;
	    zput(c, out);
	}
	break;
      default:
	ERRORRETURN("unsupported or illegal sound encoding");
    }
    return 0;
}

int
pvftobasic _P2((argc, argv), int argc, char **argv )
{
    FILE *in=stdin, *out=stdout;
    
    if( argc>1 ) USAGE("");
    
    while (1) {
	int sample;
	sample = zget(in);
	if(feof(in)) break;
	putc(linear2ulaw(sample), out);
    }

    return 0;
}

int
basictopvf _P2((argc, argv), int argc, char **argv )
{
    FILE *in=stdin, *out=stdout;
    int c;
    
    if( argc>1 ) USAGE("");
    
    while ((c = getc (in)) != EOF)
	zput (ulaw2linear (c), out);
    
    return 0;
}
