
/*
#    Sfront, a SAOL to C translator    
#    This file: aiff audio driver for sfront
#    Copyright (C) 1999  Regents of the University of California
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License (Version 2) 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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Maintainer: John Lazzaro, lazzaro@cs.berkeley.edu
*/


/****************************************************************/
/****************************************************************/
/*             aif file audio driver for sfront                 */ 
/*                  reads/writes AIFF files                     */
/****************************************************************/
        
#include <stdio.h>
#include <string.h>

#if defined(ASYS_HASOUTPUT)

/* default name for output audio file */
#define ASYSO_DEFAULTNAME "output.aif"

/* global variables, must start with asyso_ */

FILE * asyso_fd;     /* output file pointer */
char * asyso_name;   /* name of file  */        
long asyso_srate;    /* sampling rate */
long asyso_channels; /* number of channels */
long asyso_size;    /* number of samples in a buffer */
long asyso_nsamp;    /* total number of shorts written */
long asyso_doswap;   /* needs byteswap on write */
short * asyso_buf;   /* location for output buffer */ 
#endif

#if defined(ASYS_HASINPUT)

/* default name for input audio file */

#define ASYSI_DEFAULTNAME "input.aif"

/* only used for asysi_soundtypecheck */

#define ASYSI_MATCH  0
#define ASYSI_EOF 1
#define ASYSI_NOMATCH 2

/* global variables, must start with asysi_ */

FILE * asysi_fd;     /* input file pointer */
char * asysi_name;   /* name of file  */        
long asysi_srate;    /* sampling rate */
long asysi_channels; /* number of channels */
long asysi_size;    /* number of samples in a buffer */
long asysi_nsamp;    /* total number of shorts read */
long asysi_doswap;   /* needs byteswap on read */
short * asysi_buf;   /* location for input buffer */ 

#endif

#if defined(ASYS_HASOUTPUT)

/*********************************************************/
/*            writes next block of AIFF bytes            */
/*********************************************************/

int asyso_putbytes(unsigned char * c, int numbytes)

{
  if (rwrite(c, sizeof(char), numbytes, asyso_fd) != numbytes)
    return ASYS_ERROR;
  return ASYS_DONE;
}

/*********************************************************/
/*        writes unsigned long to a AIFF files            */
/*********************************************************/

int asyso_putlong(unsigned long val, int numbytes)

{
  unsigned char c[4];

  if (numbytes > 4)
    return ASYS_ERROR;
  switch (numbytes) {
  case 4:
    c[3] = (unsigned char) (val&0x000000FF);
    c[2] = (unsigned char)((val >> 8)&0x000000FF);
    c[1] = (unsigned char)((val >> 16)&0x000000FF);
    c[0] = (unsigned char)((val >> 24)&0x000000FF);
    return asyso_putbytes(c, 4);
  case 3:
    c[2] = (unsigned char) (val&0x000000FF);
    c[1] = (unsigned char)((val >> 8)&0x000000FF);
    c[0] = (unsigned char)((val >> 16)&0x000000FF);
    return asyso_putbytes(c, 3);
  case 2:
    c[1] = (unsigned char) (val&0x000000FF);
    c[0] = (unsigned char)((val >> 8)&0x000000FF);
    return asyso_putbytes(c, 2);
  case 1:
    c[0] = (unsigned char) (val&0x000000FF);
    return asyso_putbytes(c,1);
  default:
    return ASYS_ERROR;
  }

}

/****************************************************************/
/*        core routine for audio output setup                   */
/****************************************************************/

int asyso_setup(long srate, long ochannels, long osize, char * name)


{
  short swaptest = 0x0001;
  int e;
  unsigned long m;
  unsigned char c[10];
  char * val;

  asyso_doswap = *((char *)&swaptest);
  if (name == NULL)
    val = ASYSO_DEFAULTNAME;
  else
    val = name;
  asyso_name = strcpy((char *) calloc((strlen(val)+1),sizeof(char)), val);

  asyso_fd = fopen(asyso_name,"wb");
  if (asyso_fd == NULL)
    return ASYS_ERROR;

  /* preamble for wav file */

  asyso_putbytes((unsigned char *) "FORM",4);
  asyso_putlong(0,4);       /* length, patched later */
  asyso_putbytes((unsigned char *) "AIFFCOMM",8);
  asyso_putlong(18,4);                 /* 18 bytes */
  asyso_putlong(ochannels,2);          /* number of channels */
  asyso_putlong(0,4);                  /* frames, patched later */
  asyso_putlong(16,2);                 /* 16 bit data */
  m = (unsigned long)floor(ldexp(frexp((double)srate, &e),32));
  e += 16382;
  c[0] = e >> 8;
  c[1] = e;
  c[2] = m >> 24;
  c[3] = m >> 16;
  c[4] = m >> 8;
  c[5] = m;
  c[6] = c[7] = c[8] = c[9] = 0;
  asyso_putbytes((unsigned char *)&c[0],10); /* srate */
  asyso_putbytes((unsigned char *) "SSND",4);
  asyso_putlong(0,4);       /* length, patched later */
  asyso_putlong(0,4);       /* offset = 0 */           
  asyso_putlong(0,4);       /* block size */           

  asyso_srate = srate;
  asyso_channels = ochannels;
  asyso_size = osize;
  asyso_nsamp = 0;
  asyso_buf = (short *)calloc(osize, sizeof(short));
  return ASYS_DONE;
}

#endif

#if defined(ASYS_HASINPUT)

/*********************************************************/
/*            gets next block of AIFF bytes               */
/*********************************************************/

int asysi_getbytes(unsigned char * c, int numbytes)

{
  if ((int)rread(c, sizeof(char), numbytes, asysi_fd) != numbytes)
    return ASYS_ERROR;
  return ASYS_DONE;
}

/*********************************************************/
/*        flushes next block of AIFF bytes                */
/*********************************************************/

int asysi_flushbytes(int numbytes)

{
  unsigned char c;

  while (numbytes > 0)
    {
      if (rread(&c, sizeof(char), 1, asysi_fd) != 1)
	return ASYS_ERROR;
      numbytes--;
    }
  return ASYS_DONE;

}

/*********************************************************/
/*     converts byte stream to an unsigned long          */
/*********************************************************/

long asysi_getlong(int numbytes,  unsigned long * ret)

{
  unsigned char c[4];

  if (numbytes > 4)
    return ASYS_ERROR;
  if (ASYS_DONE != asysi_getbytes(&c[0],numbytes))
    return ASYS_ERROR;
  switch (numbytes) {
  case 4:
    *ret  =  (unsigned long)c[3];
    *ret |=  (unsigned long)c[2] << 8;
    *ret |=  (unsigned long)c[1] << 16;
    *ret |=  (unsigned long)c[0] << 24;
    return ASYS_DONE;
  case 3:
    *ret  =  (unsigned long)c[2];
    *ret |=  (unsigned long)c[1] << 8;
    *ret |=  (unsigned long)c[0] << 16;
    return ASYS_DONE;
  case 2:
    *ret  =  (unsigned long)c[1];
    *ret |=  (unsigned long)c[0] << 8;
    return ASYS_DONE;
  case 1:
    *ret = (unsigned long)c[0];
    return ASYS_DONE;
  default:
    return ASYS_ERROR;
  }

}
    
/*********************************************************/
/*     converts byte stream to an long          */
/*********************************************************/

long asysi_getslong(int numbytes, long * ret)

{
  unsigned char c[4];

  if (numbytes > 4)
    return ASYS_ERROR;
  if (ASYS_DONE != asysi_getbytes(&c[0],numbytes))
    return ASYS_ERROR;
  switch (numbytes) {
  case 4:
    *ret  =  (long)c[3];
    *ret |=  (long)c[2] << 8;
    *ret |=  (long)c[1] << 16;
    *ret |=  (long)c[0] << 24;
    return ASYS_DONE;
  case 3:
    *ret  =  (long)c[2];
    *ret |=  (long)c[1] << 8;
    *ret |=  (long)c[0] << 16;
    return ASYS_DONE;
  case 2:
    *ret  =  (long)c[1];
    *ret |=  (long)c[0] << 8;
    return ASYS_DONE;
  case 1:
    *ret = (long)c[0];
    return ASYS_DONE;
  default:
    return ASYS_ERROR;
  }

}
    
/***********************************************************/
/*  checks byte stream for AIFF cookie --                 */
/***********************************************************/

int asysi_soundtypecheck(char * d)

{
  char c[4];

  if (rread(c, sizeof(char), 4, asysi_fd) != 4)
    return ASYSI_EOF;
  if (strncmp(c,d,4))
    return ASYSI_NOMATCH;
  return ASYSI_MATCH;
}
  
/****************************************************************/
/*        core routine for audio input setup                   */
/****************************************************************/

int asysi_setup(long srate, long ichannels, long isize, char * name)


{
  short swaptest = 0x0001;
  unsigned long i, m;
  long e, len;
  unsigned char c[4];
  char * val;

  asysi_doswap = *((char *)&swaptest);
  if (name == NULL)
    val = ASYSI_DEFAULTNAME;
  else
    val = name;
  asysi_name = strcpy((char *) calloc((strlen(val)+1),sizeof(char)), val);
  asysi_fd = fopen(asysi_name,"rb");

  if (asysi_fd == NULL)
    return ASYS_ERROR;
  if (asysi_soundtypecheck("FORM")!= ASYSI_MATCH)
    return ASYS_ERROR;
  if (asysi_flushbytes(4)!= ASYS_DONE)
    return ASYS_ERROR;
  if (asysi_soundtypecheck("AIFF")!= ASYSI_MATCH)
    return ASYS_ERROR;
  if (asysi_getbytes(&c[0],4)!= ASYS_DONE)
    return ASYS_ERROR;
  while (strncmp((char *) c,"SSND",4))
    {
      if (strncmp((char *) c,"COMM",4))
	{
	  if (asysi_getlong(4, &i) != ASYS_DONE)
	    return ASYS_ERROR;
	  if (asysi_flushbytes(i)!= ASYS_DONE)
	    return ASYS_ERROR;
	}
      else
	{
	  if (asysi_flushbytes(4)!= ASYS_DONE) /* length */
	    return ASYS_ERROR;
	  if (asysi_getlong(2, &i) != ASYS_DONE)
	    return ASYS_ERROR;
	  if (i != ichannels)
	    {
	      fprintf(stderr,"Error: Inchannels doesn't match AIFF file\n");
	      return ASYS_ERROR;
	    }
	  if (asysi_flushbytes(4)!= ASYS_DONE) /* frames */
	    return ASYS_ERROR;
	  if (asysi_getlong(2, &i) != ASYS_DONE)
	    return ASYS_ERROR;
	  if (i != 16)
	    {
	      fprintf(stderr,"Error: Can't handle %i bit data\n",i);
	      return ASYS_ERROR;
	    }
	  if (asysi_getslong(2, &e) != ASYS_DONE)
	    return ASYS_ERROR;
	  if (asysi_getlong(4, &m) != ASYS_DONE)
	    return ASYS_ERROR;
	  if (asysi_flushbytes(4)!= ASYS_DONE) /* unneeded precision */
	    return ASYS_ERROR;
	  i = (unsigned long)(0.5+(m*exp(log(2)*(e - 16414.0F))));
	  if (srate != i)
	    fprintf(stderr,"Warning: SAOL srate %i mismatches AIFF file srate %i\n",
		    srate, i);
	    
	}
      if (asysi_getbytes(&c[0],4)!= ASYS_DONE)
	return ASYS_ERROR;
    }
  if (asysi_getlong(4, &i) != ASYS_DONE)
    return ASYS_ERROR;
  if (asysi_flushbytes(8)!= ASYS_DONE) 
    return ASYS_ERROR;
  asysi_nsamp = (i - 8)/2;
  
  asysi_srate = srate;
  asysi_channels = ichannels;
  asysi_size = isize;
  asysi_buf = (short *)malloc(sizeof(short)*isize);
  return ASYS_DONE;
}

#endif

#if (defined(ASYS_HASOUTPUT) && !defined(ASYS_HASINPUT))

/****************************************************************/
/*        sets up audio output for a given srate/channels       */
/****************************************************************/

int asys_osetup(long srate, long ochannels, long osample, 
		char * oname, long toption)

{
  return asyso_setup(srate, ochannels, ASYS_OCHAN*ACYCLE, oname);
}

#endif


#if (!defined(ASYS_HASOUTPUT) && defined(ASYS_HASINPUT))

/****************************************************************/
/*        sets up audio input for a given srate/channels       */
/****************************************************************/

int asys_isetup(long srate, long ichannels, long isample, 
		char * iname, long toption)

{
  return asysi_setup(srate, ichannels, ASYS_ICHAN*ACYCLE, iname);
}

#endif


#if (defined(ASYS_HASOUTPUT) && defined(ASYS_HASINPUT))

/****************************************************************/
/*   sets up audio input and output for a given srate/channels  */
/****************************************************************/

int asys_iosetup(long srate, long ichannels, long ochannels,
		 long isample, long osample, 
		 char * iname, char * oname, long toption)
{

  if (asysi_setup(srate, ichannels, ASYS_ICHAN*ACYCLE, iname) != ASYS_DONE)
    return ASYS_ERROR;
  return asyso_setup(srate, ochannels, ASYS_OCHAN*ACYCLE, oname);

}

#endif

#if defined(ASYS_HASOUTPUT)

/****************************************************************/
/*             shuts down audio output system                   */
/****************************************************************/

void asyso_shutdown(void)

{

  fseek(asyso_fd, 4, SEEK_SET);
  asyso_putlong(2*asyso_nsamp+46,4);
  fseek(asyso_fd,22,SEEK_SET); /* bugfix by Richard Dobson */
  asyso_putlong(asyso_nsamp /asyso_channels,4);
  fseek(asyso_fd, 16, SEEK_CUR);
  asyso_putlong(8+2*asyso_nsamp,4);
  fclose(asyso_fd);
}

#endif

#if defined(ASYS_HASINPUT)

/****************************************************************/
/*               shuts down audio input system                  */
/****************************************************************/

void asysi_shutdown(void)

{

  fclose(asysi_fd);
}

#endif


#if (defined(ASYS_HASOUTPUT)&&(!defined(ASYS_HASINPUT)))

/****************************************************************/
/*                    shuts down audio output                   */
/****************************************************************/

void asys_oshutdown(void)

{
  asyso_shutdown();
}

#endif

#if (!defined(ASYS_HASOUTPUT)&&(defined(ASYS_HASINPUT)))

/****************************************************************/
/*              shuts down audio input device                   */
/****************************************************************/

void asys_ishutdown(void)

{
  asysi_shutdown();
}

#endif

#if (defined(ASYS_HASOUTPUT)&&(defined(ASYS_HASINPUT)))

/****************************************************************/
/*              shuts down audio input and output device        */
/****************************************************************/

void asys_ioshutdown(void)

{
  asysi_shutdown();
  asyso_shutdown();
}

#endif


#if defined(ASYS_HASOUTPUT)

/****************************************************************/
/*        creates buffer, and generates starting silence        */
/****************************************************************/

int asys_preamble(ASYS_OTYPE * asys_obuf[], long * osize)

{
  int i;

  *asys_obuf = asyso_buf;
  *osize = asyso_size;
  return ASYS_DONE;
}

/****************************************************************/
/*               sends one frame of audio to output             */
/****************************************************************/

int asys_putbuf(ASYS_OTYPE * asys_obuf[], long * osize)

{
  unsigned char * buf;
  unsigned char tmp;
  long i = 0;

  if (asyso_doswap)
    {
      buf = (unsigned char *)(*asys_obuf);
      while (i < 2*(*osize))
	{
	  tmp = buf[i+1];
	  buf[i+1] = buf[i];
	  buf[i] = tmp;
	  i += 2;
	}
      if (rwrite(buf, sizeof(char), 2*(*osize), asyso_fd) != 2*(*osize))
	return ASYS_ERROR;
    }
  else
    if (rwrite(*asys_obuf, sizeof(short), *osize, asyso_fd) != *osize)
      return ASYS_ERROR;
  asyso_nsamp += *osize;
  *osize = asyso_size;
  return ASYS_DONE;
}

#endif

#if defined(ASYS_HASINPUT)

/****************************************************************/
/*               sends one frame of audio to output             */
/****************************************************************/

int asys_getbuf(ASYS_ITYPE * asys_ibuf[], long * isize)

{
  unsigned char * buf;
  unsigned char tmp;
  long i = 0;

  if (*asys_ibuf == NULL)
    *asys_ibuf = asysi_buf;
  
  if (asysi_nsamp <= 0)
    {
      *isize = 0;
      return ASYS_DONE;
    }

  *isize = (long)rread(*asys_ibuf, sizeof(short), asysi_size, asysi_fd);
  if (asysi_doswap)
    {
      buf = (unsigned char *)(*asys_ibuf);
      while (i < 2*(*isize))
	{
	  tmp = buf[i+1];
	  buf[i+1] = buf[i];
	  buf[i] = tmp;
	  i += 2;
	}
    }
  asysi_nsamp -= *isize;
  return ASYS_DONE;
}

#endif

