/*****************************************************************************/
/*                                                                           */
/* str.c  -  plays sound/noisetracker files on a SparcStation                */
/*                                                                           */
/* Author   : Liam Corner - zenith@dcs.warwick.ac.uk                         */
/*                                                                           */
/* Usage    : str <filename>                                                 */
/*            [f|z]cat filename | str                                        */
/*                                                                           */
/* ------------------------------------------------------------------------- */
/*                                                                           */
/* New Features added by Rochus Wessels - srb103@math.uni-muenster.de        */
/* Version  : 1.00r - 28 Oct 1992                                            */
/*                                                                           */
/* 27 Oct 92: -v Option added                                                */
/* 28 Oct 92: automatically decrunch .Z-Files                                */
/*            additional Options -d,-h                                       */
/*                                                                           */
/*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>

#include <strings.h>
#include <fcntl.h>
#include "/usr/demo/SOUND/multimedia/libaudio.h"
#include "/usr/demo/SOUND/multimedia/audio_device.h"


/**********************************************************/
/* uS is the number of uSeconds that a byte is played for */
/* Sparc plays at 8000 bytes/sec  =>  1 byte = 125 uSec   */
/* VSYNC is the number of bytes played in 1/50 sec        */
/* ie 0.02/(uS * 10**-6)                                  */
/**********************************************************/
#define uS 125
#define VSYNC 160
#define AUDIO "/dev/audio"

#define MIN(A,B) ((A)<(B) ? (A) : (B))
#define MAX(A,B) ((A)>(B) ? (A) : (B))


typedef struct {    /***********************************/
  char *info;       /* Sample                          */
  int length;       /* Length of sample                */
  float volume;     /* Fractional volume 0-1 (min-max) */
  int rep_start;    /* Byte offset of repeat start     */
  int rep_end;      /* Byte offset of repeat end       */
} Voice;            /***********************************/


typedef struct {                 /**************************/
  char sample [64][4];           /* Sample number          */
  char effect [64][4];           /* Effect number          */
  unsigned char params [64][4];  /* Effect parameters      */
  int period [64][4];            /* Period (pitch) of note */
} Pattern;                       /**************************/


typedef struct {         /***********************************************/
  char old;              /* Sample number of last note                  */
  char samp;             /* Sample number of current note               */
  int pitch;             /* Current channel pitch (index to step_table) */
  int slide;             /* Step size of pitch slide (if any)           */
  unsigned int pointer;  /* Current sample position                     */
  unsigned int step;     /* Sample offset increment (gives pitch)       */
  float volume;          /* Fractional volume of current note           */
} Channel;               /***********************************************/


/*****************************************************************************/
/* Skips the next 'n' input bytes - because fseek won't work on stdin        */
/*****************************************************************************/
void byteskip (fp, bytes)
FILE *fp;
int bytes;
{
  int loop;
  for (loop=0; loop<bytes; loop++)
    fgetc(fp);
}


/************************************************************************/
/*      For routine 'cvt' only                                          */
/************************************************************************/
/*      Copyright 1989 by Rich Gopstein and Harris Corporation          */
/************************************************************************/

unsigned int cvt(ch)
int ch;
{

  int mask;

  if (ch < 0) {
    ch = -ch;
    mask = 0x7f;
  } else {
    mask = 0xff;
  }

  if (ch < 32) {
    ch = 0xF0 | 15 - (ch / 2);
  } else if (ch < 96) {
    ch = 0xE0 | 15 - (ch - 32) / 4;
  } else if (ch < 224) {
    ch = 0xD0 | 15 - (ch - 96) / 8;
  } else if (ch < 480) {
    ch = 0xC0 | 15 - (ch - 224) / 16;
  } else if (ch < 992) {
    ch = 0xB0 | 15 - (ch - 480) / 32;
  } else if (ch < 2016) {
    ch = 0xA0 | 15 - (ch - 992) / 64;
  } else if (ch < 4064) {
    ch = 0x90 | 15 - (ch - 2016) / 128;
  } else if (ch < 8160) {
    ch = 0x80 | 15 - (ch - 4064) /  256;
  } else {
    ch = 0x80;
  }
return (mask & ch);
}

void printusage(void)
{
  fprintf(stderr,"\nUsage: str [<filename>]\n"
		  "Options:\n"
		  "  -b patnr. : Starts at Pattern <patnr.> (0-63)\n"
		  "  -d        : Display Samplenames\n"
		  "  -h        : Display this Helppage\n"
		  "  -l        : Loop \n"
		  "  -r patnr  : Set Restart-Pattern <patnr.> (0-63)\n"
		  "  -s speed  : Set initial Speed <speed> (1-15)\n"
		  "  -v vol    : Set Volume <vol> (0-100)\n\n");
  exit (1);
}

int main (argc, argv)
int argc;
char **argv;
{
  FILE *fp, *audio;
  int loop;
  int notes, note, channel, vsync;
  int pat, pat_num;
  int byte, bytes;
  int step_table [1024];
  int speed=6;                      /* Default speed is 6 */
  int end_pattern=0;
  char songlength;
  char tune [128];
  char num_patterns=0;
  unsigned char ulaw;
  float dummy1,dummy2;
  Voice voices [32];
  Pattern patterns [64];
  Channel ch [4];
  
  int argnum;
  int ftype;
#define FTYPE_FILE 0
#define FTYPE_STDIN 1
#define FTYPE_PIPE 2
#define MAX_SLEN 512
  char combuf[MAX_SLEN];
  int opt_restart=0;
  int opt_loop=0;
  int opt_begin=0;
  int opt_speed=-1;
  int opt_display=0;

#if 0
  if (argc>2)
    {
      fprintf(stderr,"Usage: str32 [<filename>]\n");
      exit (1);
    }
#endif

  /***************************************************************************/
  /* Creates a table of the byte_step << 16 for a given pitch                */
  /* The step and pointer are stored << 16 to get accuracy without floats    */
  /* eg to get double pitch only play every other byte                       */
  /* so step of 0x10000 is normal pitch, 0x8000 is half, 0x20000 is double   */
  /* Pointer is >> 16 when accessed, so 0x10000 is 1st byte, 0x20000 2nd etc */
  /* I have no idea where the other numbers are from, I copied them from     */
  /* a SoundTracker player for the Acorn Archimedes                          */
  /***************************************************************************/
  step_table[0]=0;
  for (loop=1; loop<1024; loop++)
    {
      dummy1=3575872/loop;
      dummy2=(dummy1/(1000000/uS))*60000;
      step_table[loop]=(int)dummy2;
    }

  {
	double vol=1.0;
	int Audio_fd=-1;
	int err=0;
	int intvol=0;
	char option=0;
	char  *Audio_dev = "/dev/audio";

	for (argnum=1;argnum<argc;argnum++)
	  {
		if (argv[argnum][0]!='-')
			break;
		option=argv[argnum][1];
		switch(option)
		  {
		  case 'b':
		  case 'r':
		  case 's':
		  case 'v':
			if (argnum==argc)
			  {
				option=0;
				printusage();
				break;
			  }
			argnum++;
			sscanf(argv[argnum],"%d",&intvol);
			break;
		  default:;
		  }
		switch(option)
		  {
		  case 'b': opt_begin=intvol;
			if (opt_begin<0 || opt_begin>63)
			  printusage();
			break;
		  case 'd': opt_display=1;break;
		  case 'h': printusage();break;
		  case 'l': opt_loop=1;break;
		  case 'r': opt_restart=intvol;opt_loop=2;
			if (opt_restart<0 || opt_restart>63)
			  printusage();
			break;
		  case 's': opt_speed=intvol;
			if (opt_speed<1 || opt_speed>15)
			  printusage();
			speed=opt_speed;
			break;
		  case 'v':
			if (intvol<0 || intvol>100)
			  printusage();
			Audio_fd = open(Audio_dev, O_WRONLY | O_NDELAY);
			if (Audio_fd>=0)
			  {
				vol=(double)intvol/100.0;
				err=audio_set_play_gain(Audio_fd,&vol);
				close(Audio_fd);
			  }
			break;
		  default:printusage();
		  }
	  }
  }

  ftype=FTYPE_FILE;
  if (argc==argnum)
	{
	  fp=stdin;
	  ftype=FTYPE_STDIN;
	}
  else
	{
#define MAX_SLEN 512
	  int slen;
	  char * s;
	  char combuf[MAX_SLEN];

	  s=argv[argnum];
	  slen=strlen(s);
	  
	  if (slen>=2 && slen<(MAX_SLEN-16))
		{
		  if (s[slen-2]=='.' && s[slen-1]=='Z')
			{
			  strcpy(combuf,"zcat ");
			  strcat(combuf,s);
			  ftype=FTYPE_PIPE;
			}
		}

	  if (ftype==FTYPE_PIPE)
		fp=popen(combuf,"r");
	  else
		fp=fopen(argv[argnum],"r");
	}
  if (fp==NULL)
    {
      fprintf(stderr,"str: unable to open tune file %s\n",argv[1]);
      exit (1);
    }


  if (opt_display)
	{
	  char s[22];
	  int i;

	  for (i=0;i<20;i++)
		s[i]=fgetc(fp);
	  s[20]=0;
	  printf("Module: %s\n",s); 
	}
  else
	byteskip(fp,20);          /* Skips over filename */

  
  /* Reads in the 31 sample-information tables */
  for (loop=1; loop<32; loop++)
    {
	  if (opt_display)
		{
		  char s[24];
		  int i;

		  for (i=0;i<22;i++)
			s[i]=fgetc(fp);
		  s[22]=0;
		  printf(" %2d: %s\n",loop,s); 
		}
	  else
		byteskip(fp,22);      /* Skips over sample name */
      voices[loop].length=((fgetc(fp)<<8)|fgetc(fp))*2;
      fgetc(fp);
      voices[loop].volume=fgetc(fp);
      voices[loop].volume=MIN(voices[loop].volume, 64);
      voices[loop].volume/=64;   /* Volume is a fraction */
      voices[loop].rep_start=((fgetc(fp)<<8)|fgetc(fp))*2;
      voices[loop].rep_end=((fgetc(fp)<<8)|fgetc(fp))*2;
      if (voices[loop].rep_end==2)
	{
	  /* If there is no repeat then start and end */
	  /* are set to the end of the sample         */
	  voices[loop].rep_end=voices[loop].length;
	  voices[loop].rep_start=voices[loop].length;
	}
      else
	{
	  /* If there is a repeat then end=start+length, but must be */
	  /* less than the sample length.  Not sure if this is 100%  */
	  /* correct, but it seems to work OK :-)                    */
	  voices[loop].rep_end+=voices[loop].rep_start;
	  voices[loop].rep_end=MIN(voices[loop].rep_end, voices[loop].length);
	}
    }
  voices[0].length=0;


  songlength=fgetc(fp);


  byteskip(fp,1);


  /* Reads in the tune */
  for (loop=0; loop<128; loop++)
    {
      tune[loop]=fgetc(fp);
      if (tune[loop]>num_patterns) num_patterns=tune[loop];
    }
  num_patterns++;


  byteskip(fp,4);


  for (pat_num=0; pat_num<num_patterns; pat_num++)  /* Reads in the patterns */
    {
      for (notes=0; notes<64; notes++)              /* 64 notes per pattern  */
	{
	  for (channel=0; channel<4; channel++)     /* 4 channels per note   */
	    {
	      note=(fgetc(fp)<<24) | (fgetc(fp)<<16) | (fgetc(fp)<<8) | fgetc(fp);
	      (patterns[pat_num]).effect[notes][channel]=(note & 0xF00) >> 8;
	      (patterns[pat_num]).params[notes][channel]=note & 0xFF;
	      (patterns[pat_num]).sample[notes][channel]=((note & 0xF000)>>12) | ((note>>24) & 0x10);
	      (patterns[pat_num]).period[notes][channel]=MIN((note & 0xFFF0000) >> 16, 1023);
	    }
	}
    }



  /* Stores the samples voices as an array of char */
  for (loop=1; loop<32; loop++)
    {
      voices[loop].info=malloc(voices[loop].length);
      if (voices[loop].info==NULL)
	{
	  fprintf(stderr, "str: unable to allocate memory\n");
	  exit (1);
	}
      fread(voices[loop].info, 1, voices[loop].length, fp);
    }

  if (feof(fp))
    {
      fprintf(stderr,"str: input file may be corrupt\n");
/*      exit (1);
*/    }


  switch(ftype)
	{
	case FTYPE_FILE: fclose(fp); break;
	case FTYPE_PIPE: pclose(fp); break;
	default:;
	}

  audio=fopen(AUDIO, "w");
  if (audio==NULL)
    {
      fprintf(stderr,"str: unable to access %s\n",AUDIO);
      exit (1);
    }

  for (loop=0; loop<4; loop++)
    {
      ch[loop].old=0;
      ch[loop].pointer=0;
      ch[loop].step=0;
      ch[loop].volume=0;
      ch[loop].pitch=0;
    }


  do {
  for (pat_num=opt_begin; pat_num<songlength; pat_num++)
    {
      pat=tune[pat_num];
      for (notes=0; notes<64; notes++)
	{
	  for (channel=0; channel<4; channel++)
	    {
	      ch[channel].samp=patterns[pat].sample[notes][channel];
	      if (!ch[channel].samp)              /* If sample number=0 */
		ch[channel].samp=ch[channel].old; /* continue last note */
	      else
		{
		  ch[channel].volume=voices[ch[channel].samp].volume;
		  ch[channel].pointer=0;
		  ch[channel].step=step_table[patterns[pat].period[notes][channel]];
		  ch[channel].pitch=patterns[pat].period[notes][channel];
		}
	      ch[channel].old=ch[channel].samp;
	      ch[channel].slide=0;
	      switch (patterns[pat].effect[notes][channel])  /* Do effects */
		{
		case 0xF : speed=patterns[pat].params[notes][channel];
		  break;
		case 0xD : end_pattern=1;
		  break;
		case 0xC : ch[channel].volume=MIN((patterns[pat].params[notes][channel]),64);
		           ch[channel].volume/=64;
		  break;
		case 2   : ch[channel].slide=patterns[pat].params[notes][channel]-1;
		  break;
		case 1   : ch[channel].slide=-patterns[pat].params[notes][channel]-1;
		  break;
		case 0   : break;
		default  : ;
		}
	    }
	  if (end_pattern)     /* Skip to end of pattern if necessary */
	    {
	      notes=64;
	      end_pattern=0;
	    }
	  else
	    for (vsync=0; vsync<speed; vsync++)       /* 1 vsync = 0.02 sec */
	      {
		for (bytes=0; bytes<VSYNC; bytes++)   /* 160*125uSec = 0.02 */
		  {
		    byte=0;
		    for (channel=0; channel<4; channel++)
		      {	  
			if ((ch[channel].pointer>>16) < voices[ch[channel].samp].rep_end)
			  {
			    /* byte = sum of (sample byte * volume) for each */
			    /* of 4 channels which mixes the sounds          */
			    byte+=(int)((voices[ch[channel].samp].info[ch[channel].pointer>>16])*(ch[channel].volume));
			    ch[channel].pointer+=ch[channel].step;
			  }
			else
			  /* If at end of sample jump to rep_start position */
			  ch[channel].pointer=voices[ch[channel].samp].rep_start<<16;
		      }
			ulaw=(unsigned char) cvt(byte*4);     /* Convert byte */
		    fputc(ulaw,audio);                /* and play the note */
		  }
		for (channel=0; channel<4; channel++)   /* Do end of vsync */
		  if (ch[channel].slide)                /* effects         */
		    {
		      ch[channel].pitch+=ch[channel].slide;
		      ch[channel].pitch=MIN(ch[channel].pitch, 1023);
		      ch[channel].pitch=MAX(ch[channel].pitch, 0);
		      ch[channel].step=step_table[ch[channel].pitch];
		    } 
	      }
	}
    } /* for */
  opt_begin=opt_restart;
  } while (opt_loop);

  
  fclose(audio);
  return (0);
}
