/**********************************************************************
audio i/o streams (.au format)

$Id: austream.c,v 1.17 2003/01/19 23:17:09 purnhage Exp $

Source file: austream.c

(c) Heiko Purnhagen 1998-2003
e-mail: purnhage@tnt.uni-hannover.de
WWW:    http://www.tnt.uni-hannover.de/~purnhage/

This module is available under the terms of the GNU Lesser General
Public License. See file COPYING-LGPL for further details. All usual
disclaimers apply!

Authors:
HP    Heiko Purnhagen, Uni Hannover <purnhage@tnt.uni-hannover.de>
NM    Nikolaus Meine, Uni Hannover <meine@tnt.uni-hannover.de>

Changes:
16-sep-98   HP/NM   born, based on au_io.c
06-aug-99   HP      fixed bug in AuOpenRead() numSample
06-jun-00   HP      added non-blocking i/o: AuOpenReadNB(), AuOpenWriteNB()
04-sep-00   HP      cleaned up
10-sep-00   HP      fixed AuReadData() EOF-handling for stdin
27-Jul-2001 HP      improved numSample handling for AuOpenRead() and AuClose()
20-Aug-2001 HP      added 16 bit raw i/o (big/little endian, skip header)
05-Nov-2001 HP      openNB returns file descriptor
                    read/writeNB return actual numSample count
18-Nov-2001 HP      much faster read/writeNB ...
**********************************************************************/

/* Audio i/o streaming with stdin/stdout support. */
/* Header format: .au (Sun audio file format, aka SND or AFsp) */
/*                or no/skip header for big/little endian 16 bit raw  */
/* Sample format: 16 bit twos complement, uniform quantisation */
/* Data size set to -1 (=unknown), fixed by AuClose if seek() successful */

/* Multi channel data is interleaved: l0 r0 l1 r1 ... */
/* Total number of samples (over all channels) is used. */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifdef MSVC
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/locking.h>
#include <share.h>
#include <io.h> /* open(), close(), write(), read() */
#else
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h> /* open(), close(), write(), read() */
#endif
#include <errno.h>

#include "austream.h"
#include "common.h"


/* ---------- declarations ---------- */

#ifndef MSVC
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif

#define SWAP_SHORT(s) (((s<<8)&0xff00)|((s>>8)&0x00ff))

#define BUFSIZE (8192)


/* ---------- declarations (structures) ---------- */

struct AuStreamStruct		/* audio stream handle */
{
  FILE *f;			/* stream handle */
  int fd;			/* -1 or file descriptor if non-blocking */
  long currentSample;		/* number of samples read/written */
  int eof;			/* eof/error flag */
  int write;			/* write (not read) flag */
  int raw;			/* 0=au 1,2=raw (1=big, 2=little endian) */
  unsigned char *buf;		/* buffer if non-blocking */
  int oddbyte;			/* -1 or value of "odd" byte to be handled */
};


/* ---------- variables ---------- */

static int AUdebugLevel = 0;	/* debug level */


/* ---------- local functions ---------- */

static void putshortB (short x, AuStream *s)
{
  register int a;

  putc((x>>8)&255,s->f);
  a = putc(x&255,s->f);
  if (a==EOF)
    s->eof = 1;
}

static short getshortB (AuStream *s)
{
  register int a,b;
  
  if (s->eof)
    return 0;
  
  a = getc(s->f);
  b = getc(s->f);
  if (b==EOF) {
    s->eof = 1;
    return 0;
  }
  return (a<<8)|b;
}

static int putcB (int c, AuStream *s)
{
  unsigned char a;

  if (s->fd==-1)
    return(putc(c,s->f));
  else {
    a = c&255;
    return((write(s->fd,&a,1)!=1)?EOF:a);
  }
}

static int getcB (AuStream *s)
{
  unsigned char a;

  if (s->fd==-1)
    return(getc(s->f));
  else
    return((read(s->fd,&a,1)!=1)?EOF:a);
}

static void putint (long x, AuStream *s)
{
  int a;

  putcB((x>>24)&255,s);
  putcB((x>>16)&255,s);
  putcB((x>>8)&255,s);
  a = putcB(x&255,s);
  if (a==EOF)
    s->eof = 1;
}

static long getint (AuStream *s)
{
  int a,b;

  if (s->eof)
    return 0;
  
  a = getcB(s)<<24;
  a |= getcB(s)<<16;
  a |= getcB(s)<<8;
  b = getcB(s);
  if (b==EOF) {
    s->eof = 1;
    return 0;
  }
  return a|b;
}

static char getbyte (AuStream *s)
{
  int a;

  if (s->eof)
    return 0;
  
  a = getcB(s);
  if (a==EOF) {
    s->eof = 1;
    return 0;
  }
  return a;
}


/* ---------- functions ---------- */

/* AuInit() */
/* Init audio i/o streams. */

void AuInit (
  int debugLevel)		/* in: debug level */
				/*     0=off  1=basic  2=full */
{
  AUdebugLevel = debugLevel;
  if (AUdebugLevel) {
    fprintf(stderr,"AuInit: debugLevel=%d\n",AUdebugLevel);
  }
}


/* AuOpenRead() */
/* Open audio stream for reading. */

AuStream *AuOpenRead (
  char *streamName,		/* in: stream name, "-" for stdin */
  int *numChannel,		/* out: number of channels */
  float *fSample,		/* out: sampling frequency [Hz] */
  long *numSample)		/* out: number of samples in stream */
				/*      or -1 if not available */
				/* returns: */
				/*  audio stream (handle) */
				/*  or NULL if error */
{
  return(AuOpenReadNB(streamName,0,0,0,
		      numChannel,fSample,numSample,(int *)NULL));
}


/* AuOpenWrite() */
/* Open audio stream for writing. */

AuStream *AuOpenWrite (
  char *streamName,		/* in: stream name, "-" for stdout */
  int numChannel,		/* in: number of channels */
  float fSample)		/* in: sampling frequency [Hz] */
				/* returns: */
				/*  audio stream (handle) */
				/*  or NULL if error */
{
  return(AuOpenWriteNB(streamName,0,0,
		       numChannel,fSample,(int *)NULL));
}


/* AuOpenReadNB() */
/* Open audio stream for reading, non-blocking mode possible. */

AuStream *AuOpenReadNB (
  char *streamName,		/* in: stream name, "-" for stdin */
  int nonBlocking,		/* in: 1 for non-blocking mode */
  int rawFormat,		/* in: 0=au 1,2=raw (1=big, 2=little endian) */
  int skipHeader,		/* in: num header bytes to skip if raw */
  int *numChannel,		/* out: number of channels */
				/*      or -1 if raw */
  float *fSample,		/* out: sampling frequency [Hz] */
				/*      or -1 if raw */
  long *numSample,		/* out: number of samples in stream */
				/*      or -1 if not available */
  int *fd)			/* out: file descriptor if nonBlocking */
				/*      or -1 otherwise (may be NULL) */
				/* returns: */
				/*  audio stream (handle) */
				/*  or NULL if error */
{
  AuStream *s;
  long h,ofs,dsize,formc,srate,nchan,filesize;

  if (AUdebugLevel)
    fprintf(stderr,"AuOpenReadNB: fileName=\"%s\"\n",streamName);

  if ((s=(AuStream*)malloc(sizeof(AuStream)))==NULL)
    CommonExit(-1,"AuOpenReadNB: Can not allocate memory");
  s->currentSample = 0;
  s->eof = 0;
  s->write = 0;
  s->oddbyte = -1;

  if (nonBlocking) {
    if ((s->buf=(unsigned char*)malloc(BUFSIZE))==NULL)
      CommonExit(-1,"AuOpenReadNB: Can not allocate buffer");
    if (streamName[0]=='-' && streamName[1]==0)
      s->fd = 0;   /* stdin */
    else
      s->fd = open(streamName,O_RDONLY);
    if (s->fd==-1) {
      CommonWarning("AuOpenReadNB: Can not open \"%s\"",streamName);
      free(s);
      return NULL;
    }
    if ((filesize=lseek(s->fd,0,SEEK_END))!=-1)
      if (lseek(s->fd,0,SEEK_SET)!=0) {
	CommonWarning("AuOpenReadNB: Can not rewind \"%s\"",streamName);
	close(s->fd);
	free(s);
	return NULL;
      }
  }
  else {
    s->buf = (unsigned char*)NULL;
    s->fd = -1;
    if (streamName[0]=='-' && streamName[1]==0)
      s->f = stdin;
    else
      s->f = fopen(streamName,"rb");
    if (s->f==NULL) {
      CommonWarning("AuOpenReadNB: Can not open \"%s\"",streamName);
      free(s);
      return NULL;
    }
    if (fseek(s->f,0,SEEK_END)!=-1) {
      filesize = ftell(s->f);
      if (fseek(s->f,0,SEEK_SET)!=0) {
	CommonWarning("AuOpenReadNB: Can not rewind \"%s\"",streamName);
	fclose(s->f);
	free(s);
	return NULL;
      }
    }
    else
      filesize = -1;
  }

  s->raw = rawFormat;
  if (!s->raw) {
    h = getint(s);
    if (h!=0x2e736e64) {		/* magic string: .snd */
      CommonWarning("AuOpenReadNB: Wrong magic string in \"%s\"",streamName);
      AuClose(s);
      return NULL;
    }
    ofs = getint(s);
    dsize = getint(s);
    formc = getint(s);		/* 3 = 16 bit uniform 2compl */
    srate = getint(s);
    nchan = getint(s);
    for (h=24; h<ofs; h++)
      getbyte(s);
    if (s->eof || nchan<1 || formc!=3 || ofs<24) {
      CommonWarning("AuOpenReadNB: "
		    "Unsupported audio format or header error in \"%s\"",
		    streamName);
      AuClose(s);
      return NULL;
    }

    *numChannel = nchan;
    *fSample = (float)srate;
  }
  else {
    dsize = -1;
    *numChannel = -1;
    *fSample = -1;
    ofs = skipHeader;
    for (h=0; h<ofs; h++)
      getbyte(s);
  }

  if (dsize>=0)
    *numSample = dsize/2;
  else {
    if (filesize>=ofs)
      *numSample = (filesize-ofs)/2;
    else
      *numSample = -1;
    if (AUdebugLevel)
      fprintf(stderr,"AuOpenReadNB: calc numSample from filesize=%ld\n",
	      filesize);
  }
  
  if (AUdebugLevel)
    fprintf(stderr,"AuOpenReadNB: numChannel=%d fSample=%.1f numSample=%ld\n",
	    *numChannel,*fSample,*numSample);

  if (s->fd!=-1) {
#ifdef MSVC
    CommonWarning("AuOpenReadNB: NONBLOCKING - Unsupported mode in WIN32!");
#else
    fcntl(s->fd,F_SETFL,fcntl(s->fd,F_GETFL,0)|O_NONBLOCK);
#endif
  }
  if (fd)
    *fd = s->fd;

  return s;
}


/* AuOpenWriteNB() */
/* Open audio stream for writing, non-blocking mode possible. */

AuStream *AuOpenWriteNB (
  char *streamName,		/* in: stream name, "-" for stdout */
  int nonBlocking,		/* in: 1 for non-blocking mode */
  int rawFormat,		/* in: 0=au 1,2=raw (1=big, 2=little endian) */
  int numChannel,		/* in: number of channels */
  float fSample,		/* in: sampling frequency [Hz] */
  int *fd)			/* out: file descriptor if nonBlocking */
				/*      or -1 otherwise (may be NULL) */
				/* returns: */
				/*  audio stream (handle) */
				/*  or NULL if error */
{
  AuStream *s;

  if (AUdebugLevel) {
    fprintf(stderr,"AuOpenWriteNB: fileName=\"%s\"\n",streamName);
    fprintf(stderr,"AuOpenWriteNB: numChannel=%d fSample=%.1f\n",
	   numChannel,fSample);
  }

  if ((s=(AuStream*)malloc(sizeof(AuStream)))==NULL)
    CommonExit(-1,"AuOpenWriteNB: Can not allocate memory");
  s->currentSample = 0;
  s->eof = 0;
  s->write = 1;
  s->oddbyte = -1;

  if (nonBlocking) {
    if ((s->buf=(unsigned char*)malloc(BUFSIZE))==NULL)
      CommonExit(-1,"AuOpenReadNB: Can not allocate buffer");
    if (streamName[0]=='-' && streamName[1]==0)
      s->fd = 1;   /* stdout */
    else
      s->fd = open(streamName,O_WRONLY|O_CREAT|O_TRUNC,00666);
    if (s->fd==-1) {
      CommonWarning("AuOpenWriteNB: Can not open \"%s\"",streamName);
      free(s);
      return NULL;
    }
  }
  else {
    s->buf = (unsigned char*)NULL;
    s->fd = -1;
    if (streamName[0]=='-' && streamName[1]==0)
      s->f = stdout;
    else
      s->f = fopen(streamName,"wb");
    if (s->f==NULL) {
      CommonWarning("AuOpenWriteNB: Can not open \"%s\"",streamName);
      free(s);
      return NULL;
    }
  }

  s->raw = rawFormat;
  if (!s->raw) {
    putint(0x2e736e64,s);	/* magic string: .snd */
    putint(28,s);		/* header size */
    putint(-1,s);		/* -1 = data size unknown */
    putint(3,s);		/* 3 = 16 bit uniform 2compl */
    putint((int)(fSample+.5),s);
    putint(numChannel,s);
    putint(0,s);		/* info string	*/

    if (s->eof) {
      CommonWarning("AuOpenWriteNB: Can not write to \"%s\"",streamName);
      AuClose(s);
      return NULL;
    }
  }

  if (s->fd!=-1) {
#ifdef MSVC
    CommonWarning("AuOpenWriteNB: NONBLOCKING - Unsupported mode in WIN32!");
#else
    fcntl(s->fd,F_SETFL,fcntl(s->fd,F_GETFL,0)|O_NONBLOCK);
#endif
  }
  if (fd)
    *fd = s->fd;

  return s;
}


/* AuReadData() */
/* Read data from audio stream. */
/* if non-blocking: fill with 0 if samples can not be read */
/*                  returns number of samples actually transfered */

long AuReadData (
  AuStream *stream,		/* in: audio stream (handle) */
  short *data,			/* out: data[] */
  long numSample)		/* in: number of samples to be read */
				/* returns: */
				/*  number of samples read */
{
  long i,j,k;
  int r,avail;
  short s;

  if (AUdebugLevel > 1)
    fprintf(stderr,"AuReadData: numSample=%ld\n",numSample);

  if (stream->write)
    CommonExit(1,"AuReadData: stream not in read mode");

  if (stream->fd==-1) {
    i = 0;
    while (i<numSample) {
      s = getshortB(stream);
      if (stream->eof)
	break;
      if (stream->raw==2)
	s = SWAP_SHORT(s);
      data[i++] = s;
    }
    stream->currentSample += i;
  }
  else {
    /* non-blocking */
    i = 0;
    avail = 1;
    while (i<numSample) {
      if (avail && !stream->eof) {
	j = min(BUFSIZE,2*(numSample-i));
	if (stream->oddbyte!=-1) {
	  stream->buf[0] = (unsigned char)(stream->oddbyte);
	  k = 1;
	}
	else
	  k = 0;
	r = read(stream->fd,stream->buf+k,j-k);
	if (r==-1 && errno==EAGAIN)
	  avail = 0;
	else {
	  if (r<=0)
	    stream->eof = 1;
	  else {
	    r += k;
	    if (r%2) {
	      r--;
	      stream->oddbyte = stream->buf[r];
	    }
	    else
	      stream->oddbyte = -1;
	  }
	}
      }
      else
	break;
      r &= -1;
      for (j=0; j<r; j+=2) {
	s = (stream->buf[j]<<8)|stream->buf[j+1];
	if (stream->raw==2)
	  s = SWAP_SHORT(s);
	data[i++] = s;
      }
      if (stream->eof)
	break;
    }
    stream->currentSample += i;
    j = i;
    while (j<numSample)
      data[j++] = 0;   /* pad with zero ... */
  }

  return i;
}


/* AuWriteData() */
/* Write data to audio stream. */
/* if non-blocking: discard samples that could not be written */
/*                  returns number of samples actually transfered */

long AuWriteData (
  AuStream *stream,		/* in: audio stream (handle) */
  short *data,			/* in: data[] */
  long numSample)		/* in: number of samples to be written */
				/* returns: */
				/*  number of samples read */
{
  long i,j,k;
  int r,avail;
  short s;

  if (AUdebugLevel > 1)
    fprintf(stderr,"AuWriteData: numSample=%ld\n",numSample);

  if (!stream->write)
    CommonExit(1,"AuWriteData: audio file not in write mode");

  if (stream->fd==-1) {
    for (i=0; i<numSample; i++) {
      s = data[i];
      if (stream->raw==2)
	s = SWAP_SHORT(s);
      putshortB(s,stream);
    }
    stream->currentSample += numSample;
  }
  else {
    /* non-blocking */
    i = 0;
    avail = 1;
    while (i<numSample) {
      if (avail && !stream->eof) {
	r = min(BUFSIZE,2*(numSample-i));
	if (stream->oddbyte!=-1) {
	  stream->buf[0] = (unsigned char)(stream->oddbyte);
	  k = 1;
	  r -= 2;
	}
	else
	  k = 0;
	for (j=0; j<r; j+=2) {
	  s = data[i+j/2];
	  if (stream->raw==2)
	    s = SWAP_SHORT(s);
	  stream->buf[j+k] = (s>>8)&255;
	  stream->buf[j+1+k] = s&255;
	}
	r = write(stream->fd,stream->buf,j);
	if (r==-1 && errno==EAGAIN)
	  avail = 0;
	else {
	  if (r<=0)
	    stream->eof = 1;
	  else {
	    if (r%2) {
	      r--;
	      stream->oddbyte = stream->buf[r];
	    }
	    else
	      stream->oddbyte = -1;
	    i += r/2;
	  }
	}
      }
      else
	break;
    }
    stream->currentSample += i;
  }

  if (stream->eof)
    CommonWarning("AuWriteDate: Can not write to au stream");

  return i;
}


/* AuClose() */
/* Close audio stream.*/

void AuClose (
  AuStream *stream)		/* in: audio stream (handle) */
{
  long dsize;

  if (AUdebugLevel)
    fprintf(stderr,"AuClose: currentSample=%ld\n",stream->currentSample);
  
  if (!stream->raw) {
    if (stream->fd==-1) {
      if (stream->write)
	if (fseek(stream->f,8,SEEK_SET)!=-1) {
	  dsize = stream->currentSample*2;
	  putint(dsize,stream);		/* write data size */
	  if (AUdebugLevel)
	    fprintf(stderr,"AuClose: fixing numSample in header\n");
	}
      if (stream->f!=stdin && stream->f!=stdout)
	fclose(stream->f);
    }
    else {
      if (stream->write)
	if (lseek(stream->fd,8,SEEK_SET)!=-1) {
	  dsize = stream->currentSample*2;
	  putint(dsize,stream);		/* write data size */
	  if (AUdebugLevel)
	    fprintf(stderr,"AuClose: fixing numSample in header\n");
	}
      if (stream->fd!=0 && stream->fd!=1)
	close(stream->fd);
    }
  }
  if (stream->buf)
    free(stream->buf);
  free(stream);
}


/* end of austream.c */
