/** EMULib Emulation Library *********************************/
/**                                                         **/
/**                          DSP.c                          **/
/**                                                         **/
/** This file contains the Unix version of sound emulation  **/
/** via /dev/dsp or /dev/audio (#define SUN_AUDIO).         **/
/**                                                         **/
/** Copyright (C) Marat Fayzullin 1996                      **/
/**     You are not allowed to distribute this software     **/
/**     commercially. Please, notify me, if you make any    **/
/**     changes to this file.                               **/
/*************************************************************/
#ifdef UNIX

#include "DSP.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>


#ifdef SUN_AUDIO  

#include <sys/audioio.h>
#include <sys/conf.h>
#include <stropts.h>

#define AUDIO_CONV(A) (ULAW[0xFF&(128+(A))]) 

#else /* SUN_AUDIO */
   
#ifdef LINUX
#include <sys/soundcard.h>
#else
#include <machine/soundcard.h>
#endif
    
#define AUDIO_CONV(A) (128+(A))

#endif /* SUN_AUDIO */


static unsigned char ULAW[256] =
{
    31,   31,   31,   32,   32,   32,   32,   33,
    33,   33,   33,   34,   34,   34,   34,   35,
    35,   35,   35,   36,   36,   36,   36,   37,
    37,   37,   37,   38,   38,   38,   38,   39,
    39,   39,   39,   40,   40,   40,   40,   41,
    41,   41,   41,   42,   42,   42,   42,   43,
    43,   43,   43,   44,   44,   44,   44,   45,
    45,   45,   45,   46,   46,   46,   46,   47,
    47,   47,   47,   48,   48,   49,   49,   50,
    50,   51,   51,   52,   52,   53,   53,   54,
    54,   55,   55,   56,   56,   57,   57,   58,
    58,   59,   59,   60,   60,   61,   61,   62,
    62,   63,   63,   64,   65,   66,   67,   68,
    69,   70,   71,   72,   73,   74,   75,   76,
    77,   78,   79,   81,   83,   85,   87,   89,
    91,   93,   95,   99,  103,  107,  111,  119,
   255,  247,  239,  235,  231,  227,  223,  221,
   219,  217,  215,  213,  211,  209,  207,  206,
   205,  204,  203,  202,  201,  200,  199,  198,
   219,  217,  215,  213,  211,  209,  207,  206,
   205,  204,  203,  202,  201,  200,  199,  198,
   197,  196,  195,  194,  193,  192,  191,  191,
   190,  190,  189,  189,  188,  188,  187,  187,
   186,  186,  185,  185,  184,  184,  183,  183,
   182,  182,  181,  181,  180,  180,  179,  179,
   178,  178,  177,  177,  176,  176,  175,  175,
   175,  175,  174,  174,  174,  174,  173,  173,
   173,  173,  172,  172,  172,  172,  171,  171,
   171,  171,  170,  170,  170,  170,  169,  169,
   169,  169,  168,  168,  168,  168,  167,  167,
   167,  167,  166,  166,  166,  166,  165,  165,
   165,  165,  164,  164,  164,  164,  163,  163
};

static int SoundFD,PipeFD[2],PeerPID;
static int SoundRate    = 0;
static int MasterVolume = 192;
static int MasterSwitch = 0xFF;
static int LoopFreq     = 25;
static int NoiseGen     = 1;
static int Verbose      = 1;

static int Volume[SND_CHANNELS];
static int Freq[SND_CHANNELS];
static int Count[SND_CHANNELS];
static int Wave[SND_BUFSIZE];

static int Type[SND_CHANNELS] =
{ 
  SND_MELODIC,SND_MELODIC,SND_MELODIC,
  SND_MELODIC,SND_MELODIC,SND_MELODIC
};


/** StopSound() **********************************************/
/** Temporarily suspend sound.                              **/
/*************************************************************/
void StopSound(void) { if(SoundRate) kill(PeerPID,SIGUSR1); }

/** ResumeSound() ********************************************/
/** Resume sound after StopSound().                         **/
/*************************************************************/
void ResumeSound(void) { if(SoundRate) kill(PeerPID,SIGUSR2); }

/** OpenSoundDevice() ****************************************/
/** Open /dev/dsp with a given level of sound quality.      **/
/** Returns 0 if failed or sound quality (Mode).            **/
/*************************************************************/
static int OpenSoundDevice(int Rate)
{
  int I,J,K;

#ifdef SUN_AUDIO

  if(Verbose) printf("  Opening /dev/audio...");
  if((SoundFD=open("/dev/audio",O_WRONLY | O_NONBLOCK))==-1)
  { if(Verbose) puts("FAILED");return(0); }

  /*
  ** Sun's specific initialization should be here...
  ** We assume, that it's set to 8000Hz u-law mono right now.
  */    

#else /* SUN_AUDIO */

  /*** At first, we need to open /dev/dsp: ***/
  if(Verbose) printf("  Opening /dev/dsp...");
  I=((SoundFD=open("/dev/dsp",O_WRONLY | O_NONBLOCK))<0);
    
  /*** Set 8-bit sound ***/
  if(!I)
  { 
    if(Verbose) printf("OK\n  Setting mode: 8bit...");
    J=AFMT_U8;I|=(ioctl(SoundFD,SNDCTL_DSP_SETFMT,&J)<0);
  }
  /*** Set mono sound ***/
  if(!I)
  {
    if(Verbose) printf("mono...");
    J=0;I|=(ioctl(SoundFD,SNDCTL_DSP_STEREO,&J)<0);
  }
  /*** Set sampling rate ***/
  if(!I)
  {
    if(Verbose) printf("OK\n  Setting sampling rate: %dHz...",Rate);
    I|=(ioctl(SoundFD,SNDCTL_DSP_SPEED,&Rate)<0);
    if(Verbose) printf("(got %dHz)...",Rate);
  } 
  /*** Here we set the number of buffers to use **/
  if(!I)
  { 
    if(Verbose)
      printf
      (
        "OK\n  Adjusting buffers: %d buffers %d bytes each...",
        SND_BUFFERS,1<<SND_BITS
      );
    J=K=SND_BITS|(SND_BUFFERS<<16);
    I|=(ioctl(SoundFD,SNDCTL_DSP_SETFRAGMENT,&J)<0);
    if(J!=K)
    {
      if(Verbose)
        printf("(got %d buffers %d bytes each)...",J>>16,1<<(J&0xFFFF));
      I=-1;
    }   
  } 
  /*** If something failed, fall out ***/
  if(I) { if(Verbose) puts("FAILED");return(0); }
    
#endif /* SUN_AUDIO */
    
  if(Verbose) puts("OK");
  return(Rate);
}

/** SoundSignal() ********************************************/
/** Signal handler for the sound server.                    **/
/*************************************************************/
static void SoundSignal(int SIG)
{
  static volatile int Suspended=0;
  switch(SIG)
  {
    case SIGUSR1:
      /* Suspend execution, until SIGUSR2 catched */
#ifndef SUN_AUDIO
      ioctl(SoundFD,SNDCTL_DSP_RESET);
#endif
      close(SoundFD);
      for(Suspended=1;Suspended;pause());
      if(Verbose) puts("SOUND: Reopening sound device");
      SoundRate=OpenSoundDevice(SoundRate);
      if(Verbose) fflush(stdout);
      break;
    case SIGUSR2:
      Suspended=0;
      break;
  }
  signal(SIG,SoundSignal);
}

/** DSPLoop() ************************************************/
/** Main loop of the sound server.                          **/
/*************************************************************/
static void DSPLoop(void)
{
  unsigned char Buf[SND_BUFSIZE],R;
  register int J,I,K,L1,L2,V;
  int FreqCount;

  for(J=0;J<SND_CHANNELS;J++) Freq[J]=Volume[J]=0;

  FreqCount=SoundRate/SND_BUFSIZE;
  for(;;FreqCount-=LoopFreq)
  {
    /* Terminate if parent process dies */
    if(PeerPID!=getppid())
    { fprintf(stderr,"SOUND: Parent died\n");exit(1); }

    /* Read data from the pipe into sound registers */
    while(J=read(PipeFD[0],Buf,4))
    {
      if(J<0)
        if(errno==EWOULDBLOCK) break;
        else { fprintf(stderr,"SOUND: Can't read from pipe\n");exit(1); }
      if(J==4)
        switch(R=Buf[0])
        {
          case 0xFF: MasterVolume=Buf[1];MasterSwitch=Buf[2];break;
          case 0xFE: R=Buf[1];
                     if(R<SND_CHANNELS) Volume[R]=Buf[2];
                     break;
          case 0xFD: R=Buf[1];
                     if(R<SND_CHANNELS)
                     {
                       Freq[R]=Buf[2]+Buf[3]*256;
                       if(Freq[R]>=SoundRate/2) Freq[R]=0;
                     }
                     break;
          case 0xFC: R=Buf[1];
                     if(R<SND_CHANNELS) Type[R]=Buf[2];
                     break;
          default:   if(R<SND_CHANNELS)
                     {
                       Volume[R]=Buf[1];
                       Freq[R]=Buf[2]+Buf[3]*256;
                       if(Freq[R]>=SoundRate/2) Freq[R]=0;
                     }
                     break;
        }
    }

    /* Waveform generator */
    for(J=0;J<SND_CHANNELS;J++)
      if(Freq[J]&&(V=Volume[J])&&(MasterSwitch&(1<<J)))
        switch(Type[J])
        {
          case SND_MELODIC: /* Melodic Sound */
            K=0x10000*Freq[J]/SoundRate;
            L1=Count[J];
            for(I=0;I<SND_BUFSIZE;I++)
            {
              L2=L1+K;
              Wave[I]+=
                V*(L1&0x8000? (L2&0x8000? 32767:0):(L2&0x8000? 0:-32767));
              L1=L2;
            }
            Count[J]=L1;
            break;

          case SND_NOISE: /* White Noise */
            K=0x10000*Freq[J]/SoundRate;
            L1=Count[J];
            for(I=0;I<SND_BUFSIZE;I++)
            {
              L1+=K;
              if(L1&0xFFFF0000)
              {
                L1&=0xFFFF;
                if((NoiseGen<<=1)&0x80000000) NoiseGen^=0x00040001;
              }
              Wave[I]+=V*(NoiseGen&1? 32767:-32767);
            }
            Count[J]=L1;
            break;
        }

    /* Mix and convert waveforms */
    for(J=0;J<SND_BUFSIZE;J++)
    {
      Buf[J]=AUDIO_CONV((Wave[J]/SND_CHANNELS)*MasterVolume/256/256/256);
      Wave[J]=0;
    }

    if(SoundFD==-1) sleep(1);
    else
    {
#ifdef SUN_AUDIO
      /* Flush output first, don't care about return status. After this
      ** write next buffer of audio data. This method produces a horrible 
      ** click on each buffer :( Any ideas, how to fix this?
      */
      ioctl(SoundFD,AUDIO_DRAIN);
      write(SoundFD,Buf,SND_BUFSIZE);
#else
      /* We'll block here until next DMA buffer becomes free. It happens 
      ** once per (1<<SND_BITS)/SoundRate seconds.
      */
      write(SoundFD,Buf,SND_BUFSIZE);
#endif
    }
  }
}

/** InitSound() **********************************************/
/** Initialize DSP. Returns Rate on success, 0 otherwise.   **/
/** Mode is 0 to skip initialization (will be silent).      **/
/*************************************************************/
int InitSound(int Rate,int Verb)
{
  SoundRate=0;SoundFD=-1;PeerPID=0;Verbose=Verb;
  if(Rate<=0) return(0);
  if(Rate<8192) Rate=22050;

  /* If sound was initialized, kill it */
  TrashSound();

  /* Open sound device */
  if(Verbose) puts("Starting sound server:");
  if(!(Rate=OpenSoundDevice(Rate))) return(0);

  if(Verbose) printf("  Opening pipe...");
  if(pipe(PipeFD)==-1) { if(Verbose) puts("FAILED");return(0); }

  if(Verbose) { printf("OK\n  Forking...");fflush(stdout); }
  switch(PeerPID=fork())
  {
    case -1: if(Verbose) puts("FAILED");
             return(0);
    case 0:  close(PipeFD[1]);
             fcntl(PipeFD[0],F_SETFL,O_NONBLOCK);
             Verbose&=0x80;
             PeerPID=getppid();
             SoundRate=Rate;
             signal(SIGUSR1,SoundSignal);
             signal(SIGUSR2,SoundSignal);
             DSPLoop();
             exit(0);
    default: if(Verbose) puts("OK");
             close(PipeFD[0]);
             fcntl(PipeFD[1],F_SETFL,O_NONBLOCK);
             close(SoundFD);
  }

  SetChannels(192,0xFF);
  return(SoundRate=Rate);
}

/** TrashSound() *********************************************/
/** Shut DSP down.                                          **/
/*************************************************************/
void TrashSound(void)
{
  int J;

  StopSound();
  if(PeerPID) { kill(PeerPID,SIGKILL);wait(&J); }
  if(SoundFD!=-1) close(SoundFD);
  SoundRate=0;
}

/** Sound() **************************************************/
/** Generate sound of given frequency (Hz) and volume       **/
/** (0..255) via given channel.                             **/
/*************************************************************/
void Sound(int Channel,int Freq,int Volume)
{
  unsigned char Buf[4];

  if(SoundRate)
  {
    Buf[0]=Channel;
    Buf[1]=Volume;
    Buf[2]=Freq&0xFF;
    Buf[3]=Freq>>8;
    write(PipeFD[1],Buf,4);
  }
}

/** SetChannels() ********************************************/
/** Set master volume (0..255) and turn channels on/off.    **/
/** Each bit in Toggle corresponds to a channel (1=on).     **/
/*************************************************************/
void SetChannels(int Volume,int Toggle)
{
  unsigned char Buf[4];

  if(SoundRate)
  {
    Buf[0]=0xFF;Buf[1]=Volume;
    Buf[2]=Toggle;Buf[3]=0x00;
    write(PipeFD[1],Buf,4);
  }
}

/** SetVolume() **********************************************/
/** Set volume (0..255) for a given channel.                **/
/*************************************************************/
void SetVolume(int Channel,int Volume)
{
  unsigned char Buf[4];

  if(SoundRate)
  {
    Buf[0]=0xFE;Buf[1]=Channel;
    Buf[2]=Volume;Buf[3]=0x00;
    write(PipeFD[1],Buf,4);
  }
}

/** SetFreq() ************************************************/
/** Set frequency (Hz) for a given channel.                 **/
/*************************************************************/
void SetFreq(int Channel,int Freq)
{
  unsigned char Buf[4];

  if(SoundRate)
  {
    Buf[0]=0xFD;Buf[1]=Channel;
    Buf[2]=Freq&0xFF;Buf[3]=Freq>>8;
    write(PipeFD[1],Buf,4);
  }
}

/** SetSound() ***********************************************/
/** Set sound type (SND_NOISE/SND_MELODIC) for a given      **/
/** channel.                                                **/
/*************************************************************/
void SetSound(int Channel,int Type)
{
  unsigned char Buf[4];

  if(SoundRate)
  {
    Buf[0]=0xFC;Buf[1]=Channel;
    Buf[2]=Type;Buf[3]=0x00;
    write(PipeFD[1],Buf,4);
  }
}

#endif /* UNIX */
