/*
 *  @(#) obuffer.cc 1.17, last edit: 6/27/94 13:11:31
 *  @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de)
 *  @(#) Berlin University of Technology
 *
 *  Idea and first implementation for u-law output with fast downsampling by
 *  Jim Boucher (jboucher@flash.bu.edu)
 *
 *  LinuxObuffer class written by
 *  Louis P. Kruger (lpkruger@phoenix.princeton.edu)
 *
 *  IffObuffer class written by
 *  Erik Johannessen (erik2@afrodite.kih.no)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *  Changes from version 1.1 to 1.2:
 *    - new LinuxObuffer class, which works with 16 bit soundcards
 *      like Gravis Ultrasound, SoundBlaster 16 or Pro Audio Spectrum 16,
 *      if attached to /dev/dsp
 *    - ShortObuffer renamed to FileObuffer
 *    - If ULAW is defined:
 *      - SparcObuffer feeds mono u-law output to an amd device (u-law format).
 *        The required downsampling to 8 kHz in done in the synthesis filter.
 *      - FileObuffer creates u-law output at 8 kHz instead of 16 bit PCM samples.
 *    - O_NDELAY flag is cleared now after a successful open syscall
 *      on /dev/audio (caused problems under Solaris)
 *    - options -us, -uh and -ul should work now
 *    - FileObuffer can handle incomplete write syscalls now
 */

/*
 *  19. Feb. 1995: IffObuffer class added by Erik Johannessen
 *
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <iostream.h>
#include "obuffer.h"
#include "header.h"
#ifdef ULAW
#include "ulaw.h"
#endif
#ifdef Iff
typedef long LONG;
typedef unsigned long ULONG;
typedef unsigned short UWORD;
typedef unsigned char UBYTE;
/* ---------- Voice8Header ---------------------------------------------*/
typedef LONG Fixed;	/* A fixed-point value, 16 bits to the left of
			 * the point and 16 to the right. A Fixed is a
			 * number of 2**16ths, i.e. 65536ths. */
#define Unity 0x10000L	/* Unity = Fixed 1.0 = maximum volume */

/* sCompression: Choice of compression algorithm applied to the samples. */
#define sCmpNone       0	/* not compressed */
#define sCmpFibDelta   1	/* Fibonacci-delta encoding (Appendix C) */
				/* Could be more kinds in the future. */
typedef struct {
    ULONG oneShotHiSamples,	/* # samples in the high octave 1-shot part */
          repeatHiSamples,	/* # samples in the high octave repeat part */
          samplesPerHiCycle;	/* # samples/cycle in high octave, else 0 */
    UWORD samplesPerSec;	/* data sampling rate */
    UBYTE ctOctave,		/* # of octaves of waveforms */
          sCompression;		/* data compression technique used */
    Fixed volume;		/* playback nominal volume from 0 to Unity
				 * (full volume). Map this value into
				 * the output hardware's dynamic range.
				 */
    } Voice8Header;
#endif // Iff

#ifdef SunOS
extern "C" int ioctl (int, int ...);		// Why...???
#endif


FileObuffer::FileObuffer (uint32 number_of_channels)
{
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS)
  {
    cerr << "FileObuffer: number of channels has to be in [1, " <<  MAXCHANNELS << "] !\n";
    exit (1);
  }
#endif

#ifdef ULAW
  if (number_of_channels > 1)
    cerr << "Are you sure you need stereo u-law output?\n";
#endif
  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i)
    bufferp[i] = buffer + i;
}


void FileObuffer::append (uint32 channel, int16 value)
{
#ifdef DEBUG
  if (channel >= channels)
  {
    cerr << "illegal channelnumber in FileObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE)
  {
    cerr << "FileObuffer: buffer overflow!\n";
    exit (1);
  }
#endif

#ifdef ULAW
  // convert 16-bit PCM sample to 8-bit ulaw:
  *bufferp[channel] = linear2ulaw[value >> 3];
#else
  *bufferp[channel] = value;
#endif
  bufferp[channel] += channels;
}


void FileObuffer::write_buffer (int fd)
{
  int length = (int)((char *)bufferp[0] - (char *)buffer), writelength;

  if ((writelength = write (fd, (char *)buffer, length)) != length)
  {
    // buffer has not been written completely
    if (writelength < 0)
    {
      perror ("write");
      exit (1);
    }
    length -= writelength;
    char *buffer_pos = (char *)buffer;
    do
    {
      buffer_pos += writelength;
      if ((writelength = write (fd, buffer_pos, length)) < 0)
      {
	perror ("write");
	exit (1);
      }
    }
    while (length -= writelength);
  }

  for (int i = 0; i < channels; ++i)
    bufferp[i] = buffer + i;
}

#ifdef Iff
// This Iff 8SVX code is just a quick hack.
// Please note that the stereo output mode hasn't been testet much.
// Erik Johannessen (erik2@afrodite.kih.no)
IffObuffer::IffObuffer (uint32 number_of_channels, Header *header, char *filename)
{
  unsigned long dummy=0;
Voice8Header vhdr;
	char *fname;
	long chan_type;
	char tmpstr[32];

  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i)
    bufferp[i] = buffer + i*(OBUFFERSIZE>>1);

	if(channels==2){
		chan_type=6L;
		strcpy(fname2,"/tmp/maplay.");
		sprintf(tmpstr,"%ld",getpid());
		strcat(fname2,tmpstr);
		fout2=fopen(fname2,"wb+");
		if(fout2==NULL){
			cerr << "Iff can't open output file for channel 1\n";	
			exit(1);
		}
	}
	else 
		chan_type=2L;

	fname=(char *)malloc(strlen(filename)+6);
	strcpy(fname,filename);
	strcat(fname,".8svx");
	fout=fopen(fname,"wb");
	if(fout==NULL){
		cerr << "Iff can't open output file for channel 0\n";
		exit(1);
	}
	
	free(fname);
	fwrite("FORM",1,4,fout);
	fwrite(&dummy,sizeof(dummy),1,fout);
	fwrite("8SVX",1,4,fout);
	fwrite("VHDR",1,4,fout);
	dummy=sizeof(vhdr);
	fwrite(&dummy,sizeof(dummy),1,fout);
	vhdr.oneShotHiSamples=0;
	vhdr.repeatHiSamples=0;
	vhdr.samplesPerHiCycle=0;
	vhdr.samplesPerSec=header->frequency();
	vhdr.ctOctave=1;
	vhdr.sCompression=sCmpNone;
	vhdr.volume=Unity;
	fwrite(&vhdr,sizeof(vhdr),1,fout);
	dummy=sizeof(chan_type);
	fwrite("CHAN",1,4,fout);
	fwrite(&dummy,sizeof(dummy),1,fout);
	fwrite(&chan_type,sizeof(chan_type),1,fout);
	fwrite("BODY",1,4,fout);
	dummy=0;
	fwrite(&dummy,sizeof(dummy),1,fout);
	totbytes=0;
}

IffObuffer::~IffObuffer(void)
{
	unsigned long size;
	char trbuf[2048];
	long cnt;

	if(channels==2){	
		fseek(fout2,0,SEEK_SET);
		do{
			cnt=fread(trbuf,1,2048,fout2);
			fwrite(trbuf,1,cnt,fout);
		}while(cnt==2048);
		fclose(fout2);
		remove(fname2);
	}
	size=totbytes+4+4+sizeof(Voice8Header)+12+4+4+4;
	fseek(fout,4,SEEK_SET);
	fwrite(&size,sizeof(size),1,fout);
	fseek(fout,4+4+4+4+4+sizeof(Voice8Header)+12+4,SEEK_SET);
	fwrite(&totbytes,sizeof(totbytes),1,fout);
	fseek(fout,4+4+4+4+4,SEEK_SET);
	fwrite(&totbytes,sizeof(totbytes),1,fout);
	fclose(fout);
}

void IffObuffer::append(uint32 channel,int16 value)
{
//	cerr << "channel " << channel << "\n";
  *bufferp[channel] = value>>8;
  bufferp[channel] ++;
}

void IffObuffer::write_buffer(int)
{
	int length = (int)((char *)bufferp[0] - (char *)buffer);
	fwrite((char *)buffer,length,1,fout);
	if(channels==2){
		fwrite((char *)buffer+(OBUFFERSIZE>>1),length,1,fout2);
		length<<=1;
	}
	totbytes+=length;
  for (int i = 0; i < channels; ++i)
    bufferp[i] = buffer + i*(OBUFFERSIZE>>1);
}
#endif // Iff


#ifdef Indigo
IndigoObuffer::IndigoObuffer (uint32 number_of_channels, Header *header)
{
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS)
  {
    cerr << "IndigoObuffer: number of channels has to be in [1, " <<  MAXCHANNELS << "] !\n";
    exit (1);
  }
#endif
  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i)
    bufferp[i] = buffer + i;

  // open an audio port and configure it:
  ALconfig config;
  if (!(config = ALnewconfig ()))
  {
    cerr << "ALnewconfig failed!\n";
    exit (1);
  }
  ALsetwidth (config, AL_SAMPLE_16);
  if (channels == 1)
    ALsetchannels (config, AL_MONO);
  else
    ALsetchannels (config, AL_STEREO);
  if (!(port = ALopenport ("MPEG audio player", "w", config)))
  {
    cerr << "can't allocate an audio port!\n";
    exit (1);
  }

  // set sample rate:
  long pvbuffer[2] = { AL_OUTPUT_RATE, 0 };
  pvbuffer[1] = header->frequency ();
  ALsetparams (AL_DEFAULT_DEVICE, pvbuffer, 2);
  ALfreeconfig (config);
}


IndigoObuffer::~IndigoObuffer (void)
{
  while (ALgetfilled (port) > 0)
    sleep (1);
  ALcloseport (port);
}


void IndigoObuffer::append (uint32 channel, int16 value)
{
#ifdef DEBUG
  if (channel >= channels)
  {
    cerr << "illegal channelnumber in IndigoObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE)
  {
    cerr << "IndigoObuffer: buffer overflow!\n";
    exit (1);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}


void IndigoObuffer::write_buffer (int)
{
  ALwritesamps (port, buffer, (long)(bufferp[0] - buffer));
  for (int i = 0; i < channels; ++i)
    bufferp[i] = buffer + i;
}
#endif // Indigo


#ifdef SPARC
int SparcObuffer::audio_fd = -1;

#ifdef ULAW
SparcObuffer::SparcObuffer (Header *header, bool use_speaker, bool use_headphone, bool use_line_out)
#else
SparcObuffer::SparcObuffer (uint32 number_of_channels, Header *header,
			    bool use_speaker, bool use_headphone, bool use_line_out)
#endif
{
#ifndef ULAW
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS)
  {
    cerr << "SparcObuffer: 0 < number of channels < " << MAXCHANNELS << "!\n";
    exit (1);
  }
#endif
#endif	// !ULAW

  if (audio_fd < 0)
  {
    cerr << "Internal error: SparcObuffer::audio_fd has to be initialized\n"
	    "by SparcObuffer::class_suitable()!\n";
    exit (1);
  }

  audio_info info;
  AUDIO_INITINFO (&info);
#ifndef SunOS4_1_1
  info.output_muted = False;
#endif
  info.play.port = 0;
  if (use_speaker)
    info.play.port |= AUDIO_SPEAKER;
  if (use_headphone)
    info.play.port |= AUDIO_HEADPHONE;
#ifndef SunOS4_1_1
  if (use_line_out)
    info.play.port |= AUDIO_LINE_OUT;
#endif

#ifdef ULAW
  bufferp = buffer;

  // configure the amd device:
  info.play.encoding = AUDIO_ENCODING_ULAW;
  info.play.precision = 8;
  info.play.channels = 1;
  info.play.sample_rate = 8000;
#else
  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i)
    bufferp[i] = buffer + i;

  // configure the dbri device:
  info.play.encoding = AUDIO_ENCODING_LINEAR;
  info.play.precision = 16;
  info.play.channels = channels;
  info.play.sample_rate = header->frequency ();
#endif	// !ULAW

  if (ioctl (audio_fd, AUDIO_SETINFO, &info))
  {
    perror ("configuration of /dev/audio failed");
    exit (1);
  }
}


SparcObuffer::~SparcObuffer (void)
{
  ioctl (audio_fd, AUDIO_DRAIN, NULL);
  close (audio_fd);
}


void SparcObuffer::append (uint32 channel, int16 value)
{
#ifdef ULAW
#ifdef DEBUG
  if (bufferp - buffer >= OBUFFERSIZE >> 1)
  {
    cerr << "SparcObuffer: buffer overflow!\n";
    exit (1);
  }
#endif

  // convert 16-bit PCM sample to 8-bit ulaw:
  *bufferp++ = linear2ulaw[value >> 3];
#else
#ifdef DEBUG
  if (channel >= channels)
  {
    cerr << "illegal channelnumber in SparcObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE)
  {
    cerr << "SparcObuffer: buffer overflow!\n";
    exit (1);
  }
#endif

  *bufferp[channel] = value;
  bufferp[channel] += channels;
#endif	// !ULAW
}


void SparcObuffer::write_buffer (int)
{
#ifdef ULAW
  int length = (int)((char *)bufferp - (char *)buffer);
#else
  int length = (int)((char *)*bufferp - (char *)buffer);
#endif
  if (write (audio_fd, (char *)buffer, length) != length)
  {
    perror ("write to /dev/audio failed");
    exit (1);
  }
#ifdef ULAW
  bufferp = buffer;
#else
  for (int i = 0; i < channels; ++i)
    bufferp[i] = buffer + i;
#endif
}


int SparcObuffer::open_audio_device (void)
{
  int fd;

  if ((fd = open ("/dev/audio", O_WRONLY | O_NDELAY, 0)) < 0)
    if (errno == EBUSY)
    {
      cerr << "Sorry, the audio device is busy!\n";
      exit (1);
    }
    else
    {
      perror ("can't open /dev/audio for writing");
      exit (1);
    }

  // turn NDELAY mode off:
  int flags;
  if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
  {
    perror ("fcntl F_GETFL on /dev/audio failed");
    exit (1);
  }
  flags &= ~O_NDELAY;
  if (fcntl (fd, F_SETFL, flags) < 0)
  {
    perror ("fcntl F_SETFL on /dev/audio failed");
    exit (1);
  }
  return fd;
}


#ifdef Solaris
void SparcObuffer::get_device_type (int fd, audio_device *devtype)
{
  if (ioctl (fd, AUDIO_GETDEV, devtype))
  {
    perror ("ioctl AUDIO_GETDEV on /dev/audio");
    exit (1);
  }
}
#else
int SparcObuffer::get_device_type (int fd)
{
#ifdef AUDIO_GETDEV
  int devtype;
  if (ioctl (fd, AUDIO_GETDEV, &devtype))
  {
    perror ("ioctl AUDIO_GETDEV on /dev/audio");
    exit (1);
  }
  return devtype;
#else
  cerr << "SparcObuffer::get_device_type(): AUDIO_GETDEV ioctl not available!\n";
  return -1;
#endif
}
#endif	// !Solaris


#ifdef ULAW
bool SparcObuffer::class_suitable (uint32 number_of_channels, bool force_amd)
#else
bool SparcObuffer::class_suitable (void)
#endif
{
#ifdef ULAW
  if (number_of_channels > 1)
  {
    cerr << "Your audio hardware cannot handle more than one audio channel.\n"
	    "Please use the option -l or -r for stereo streams.\n";
    return False;
  }
#endif

  // check for the dbri audio device:
  audio_fd = open_audio_device ();

#ifdef ULAW
  if (force_amd)
    return True;
#endif

#ifdef Solaris
  audio_device devtype;
  get_device_type (audio_fd, &devtype);
# ifdef ULAW
    if (!strcmp (devtype.name, "SUNW,am79c30"))
      return True;
    else if (!strcmp (devtype.name, "SUNW,dbri"))
    {
      cerr << "Your machine can produce CD-quality audio output,\n"
	      "but this binary was compiled for 8 kHz u-law ouput. (telephone quality)\n"
	      "Please recompile it without the ULAW define in COMPILERFLAGS.\n"
	      "(or use the -amd option to use this binary with low-quality output)\n";
      close (audio_fd);
      return False;
    }
# else
    if (!strcmp (devtype.name, "SUNW,dbri"))
      return True;
    else if (!strcmp (devtype.name, "SUNW,am79c30"))
    {
      cerr << "Your machine can produce 8 kHz u-law audio output only,\n"
	      "but this binary was compiled for CD-quality output.\n"
	      "Please recompile it with ULAW defined in COMPILERFLAGS\n"
	      "or use it in stdout mode as an decoder only.\n";
      close (audio_fd);
      return False;
    }
# endif
#else
  // !Solaris
# ifdef SunOS4_1_1
    // no AUDIO_GETDEV under SunOS 4.1.1, so we have to assume that there is
    // an amd device attached to /dev/audio
#   ifdef ULAW
      return True;
#   else
      cerr << "Your machine can produce 8 kHz u-law audio output only,\n"
	      "but this binary was compiled for CD-quality output.\n"
	      "Please recompile it with ULAW defined in COMPILERFLAGS\n"
	      "or use it in stdout mode as an decoder only.\n";
      close (audio_fd);
      return False;
#   endif	// !ULAW
# else
    // SunOS 4.1.3
    int device_type = get_device_type (audio_fd);
#   ifdef ULAW
      if (device_type == AUDIO_DEV_AMD)
	return True;
      else if (device_type == AUDIO_DEV_SPEAKERBOX)
      {
	cerr << "Your machine can produce CD-quality audio output,\n"
		"but this binary was compiled for 8 kHz u-law ouput. (telephone quality)\n"
		"Please recompile it without the ULAW define in COMPILERFLAGS.\n"
		"(or use the -amd option to use this binary with low-quality output)\n";
	close (audio_fd);
	return False;
      }
#   else
      if (device_type == AUDIO_DEV_SPEAKERBOX)
	return True;
      else if (device_type == AUDIO_DEV_AMD)
      {
	cerr << "Your machine can produce 8 kHz u-law audio output only,\n"
		"but this binary was compiled for CD-quality output.\n"
		"Please recompile it with ULAW defined in COMPILERFLAGS\n"
		"or use it in stdout mode as an decoder only.\n";
	close (audio_fd);
	return False;
      }
#   endif	// !ULAW
# endif	// !SunOS4_1_1
#endif	// !Solaris

#ifndef SunOS4_1_1
  close (audio_fd);
  cerr << "Sorry, I don't recognize your audio device.\n"
# ifdef ULAW
	  "Please try the -amd option or use the stdout mode.\n";
# else
	  "Please use the stdout mode.\n";
# endif
  return False;
#endif
}

#endif	// SPARC


#ifdef LINUX
int LinuxObuffer::audio_fd = -1;

int LinuxObuffer::open_audio_device (void)
{
  int fd;

  if ((fd = open ("/dev/dsp", O_WRONLY | O_NDELAY, 0)) < 0)
    if (errno == EBUSY)
    {
      cerr << "Sorry, the audio device is busy!\n";
      exit (1);
    }
    else
    {
      perror ("can't open /dev/dsp for writing");
      exit (1);
    }

  // turn NDELAY mode off:
  int flags;
  if ((flags = fcntl (fd, F_GETFL, 0)) < 0)
  {
    perror ("fcntl F_GETFL on /dev/audio failed");
    exit (1);
  }
  flags &= ~O_NDELAY;
  if (fcntl (fd, F_SETFL, flags) < 0)
  {
    perror ("fcntl F_SETFL on /dev/audio failed");
    exit (1);
  }
  return fd;
}


LinuxObuffer::LinuxObuffer (uint32 number_of_channels, Header *header)
{
#ifdef DEBUG
  if (!number_of_channels || number_of_channels > MAXCHANNELS)
  {
    cerr << "LinuxObuffer: 0 < number of channels < " << MAXCHANNELS << "!\n";
    exit (1);
  }
#endif
  channels = number_of_channels;
  for (int i = 0; i < number_of_channels; ++i)
    bufferp[i] = buffer + i;

  if (audio_fd < 0)
  {
    cerr << "Internal error, LinuxObuffer::audio_fd has to be initialized\n"
	    "by LinuxObuffer::class_suitable()!\n";
    exit (1);
  }

  // configure the device:
  int play_precision = 16;
  int play_stereo = channels-1;
  int play_sample_rate = header->frequency ();

  if(
      ioctl(audio_fd, SNDCTL_DSP_SAMPLESIZE, &play_precision) == -1 ||
      ioctl(audio_fd, SNDCTL_DSP_STEREO, &play_stereo) == -1 ||
      ioctl(audio_fd, SNDCTL_DSP_SPEED, &play_sample_rate) == -1
    )
  {
    perror ("configuration of /dev/dsp failed");
    exit (1);
  }
}


LinuxObuffer::~LinuxObuffer (void)
{
  sleep (1);
  close (audio_fd);
}


void LinuxObuffer::append (uint32 channel, int16 value)
{
#ifdef DEBUG
  if (channel >= channels)
  {
    cerr << "illegal channelnumber in LinuxObuffer::append()!\n";
    exit (1);
  }
  if (bufferp[channel] - buffer >= OBUFFERSIZE)
  {
    cerr << "buffer overflow!\n";
    exit (1);
  }
#endif
  *bufferp[channel] = value;
  bufferp[channel] += channels;
}


void LinuxObuffer::write_buffer (int)
{
  int length = (int)((char *)bufferp[0] - (char *)buffer);
  if (write (audio_fd, buffer, length) != length)
  {
    perror ("write to /dev/dsp failed");
    exit (1);
  }
  for (int i = 0; i < channels; ++i)
    bufferp[i] = buffer + i;
}


bool LinuxObuffer::class_suitable (uint32 number_of_channels)
{
  // open the dsp audio device:
  audio_fd = open_audio_device ();
  return True;
}

#endif	/* LINUX */
