; /*
gcc -O -noixemul wavepak.c -o wavepak
quit 0 ; */
/*************************************************************************\
  wavepak.c:
    Fibonacci Delta/Exponential Delta encoder for IFF 8SVX samples.
    Already compressed samples are decompressed instead.
    Both FDC and EDC are fast and efficient (but lossy!) compression 
    techniques: They achieve a guaranteed compression ratio of 50 %, but 
    at the cost of some 'quantization' noise.
    Exponential Delta differs from the well-known Fibonacci Delta
    compression only by the compression table that it uses.
  arguments and their meanings:
    <infile>
    <outfile>
    -f           Fibonacci Delta compression
    -e           Exponential Delta compression
    -v           verbose
  <outfile> defaults to <infile>.out
\*************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

char version[] = "$VER: wavepak 1.3 (26.10.96)";

#define ID_FORM 0x464F524D
#define ID_8SVX 0x38535658
#define ID_VHDR 0x56484452
#define ID_CHAN 0x4348414E
#define ID_BODY 0x424F4459

typedef unsigned long ULONG;
typedef unsigned short UWORD;
typedef unsigned char UBYTE;

void maketables();
ULONG getlong();
void handle_iff();
void help(char *s);

#define SBUFSIZE 20000
#define IOBUFSIZE 16384      /* we'll tell C to use bigger file buffers */
signed char sbuf[SBUFSIZE];      /* sample values */
unsigned char cbuf[SBUFSIZE/2];    /* compressed sample */

FILE *infile, *outfile;
char* modename[] = { "fibonacci", "exponential" };
int mode = 0, stereo = 0, verbose = 0;
signed char qtab[512];  /* quantization table */
signed char dtab[256][256];  /* delta table for all possible combinations */
/* decompression tables: */
signed char fibtab[] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21},
            exptab[] = {-128,-64,-32,-16,-8,-4,-2,-1,0,1,2,4,8,16,32,64};

main (int argc, char* argv[])
{
  ULONG l;
  int i,rawmode;
  long rawsize;
  char c, *s;
  char name1[80], name2[80];
  
  name1[0] = name2[0] = '\0';
  while (--argc) {
    s = *++argv;
    if (*s != '-') {
      if (name1[0] == '\0')
        strcpy(name1, s);
      else
        strcpy(name2, s);
    } else {
      switch (*++s) {
        case 'f': mode = 1; break;
        case 'e': mode = 2; break;
        case 'v': verbose = 1; break;
        default:
          help(argv[0]);
          return 5;
      }
    }
  }
  if (name1[0] == '\0') {
    help(NULL);
    return 5;
  }
  if (name2[0] == '\0') {
    strcpy(name2, name1);
    strcat(name2, ".out");
  }
  if (!(infile = fopen(name1,"r"))) {
    printf("Can't open '%s'!\n", name1);
    return 10;
  }
  setvbuf(infile, NULL, _IOFBF, IOBUFSIZE);
  if (getlong() != ID_FORM) {
    printf("Not an IFF file: %s!\n", name1);
    fclose(infile);
    return 5;
  } 
  l = getlong();  /* skip file size */
  if (getlong() != ID_8SVX) {
    printf("Not an 8SVX file: %s!\n", name1);
    fclose(infile);
    return 5;
  }
  if (!(outfile = fopen(name2,"w"))) {
    printf("Can't open '%s' for output!\n", name2);
    fclose(infile);
    return 10;
  }
  setvbuf(outfile, NULL, _IOFBF, IOBUFSIZE);
  /* tell the user what his parameters will do */
  if (mode) printf("C"); else printf("Unc");
  printf("ompressing '%s' as '%s'", name1, name2);
  if (mode) printf(",\n  using %s delta compression", modename[mode-1]);
  printf(" ...\n");
  rewind(infile);
  maketables();
  handle_iff();
  fclose(infile);
  fclose(outfile);

  return 0;
}

void maketables()
{
  int i,j,j2,mj,mj2,k,l;

  /* fibonacci conversion table: */
  for (i=0; i<512; i++)
    qtab[i] = 0;
  for (i=1; i<16; i++) {
    j = 256 + fibtab[i];
    k = 256 + fibtab[i-1];
    if (fibtab[i]<0) {
      while (j>k)
        qtab[j--] = i;
    } else {
      while (j<512)
        qtab[j++] = i;
    }
  }
  /* huge table for the exponential compression: */
  for (i=-128; i<128; i++) {
    for (j=0; j<256; j++)  dtab[i+128][j] = 0;
    j2 = 0;
    for (j=1; j<=8; j++) {   /* looks at the negative half of the table */
      k = i + exptab[j2]; if (k<-128) k += 256;
      l = i + exptab[j];  if (l<-128) l += 256;
      if (k<l) {
        while (k<=l) {
          dtab[128+i][128+k++] = j2;
          dtab[128+i][128+l--] = j;
        }
      } else {
        while (k<128)  dtab[128+i][128+k++] = j2;
        while (l>-129) dtab[128+i][128+l--] = j;
      }
      l = i - exptab[j];  if (l>127) l -= 256;
      k = i - exptab[j2]; if (k>127) k -= 256;
      mj = (16-j) & 0xf;
      mj2 = (16-j2) & 0xf;
      if (l<k) {
        while (l<=k) {
          dtab[128+i][128+l++] = mj;
          dtab[128+i][128+k--] = mj2;
        }
      } else {
        while (l<128)  dtab[128+i][128+l++] = mj;
        while (k>-129) dtab[128+i][128+k--] = mj2;
      }
      j2 = j;
    }
  }
}

int ctrl_c = 0;

void oops()
/* break-handler during filter operation */
{
  ctrl_c = 1;
}

long pack_body(long len, char *info)
/* Compresses a complete mono sample *or* one stereo track, */
/* return value is the number of bytes written. */
{
  long ibuf, obuf, i, outlen = 0;
  int delta;
  signed char c0, c;  /* for the sample bytes */
  unsigned char n, o;

  signal(SIGINT, *oops);  /* intercept user break (Ctrl-C) */
  ibuf = len % SBUFSIZE;       /* size of first packet */
  c0 = 0; if (ibuf & 1) {       /* can't compress odd sizes */
    c0 = fgetc(infile); len--; ibuf--;
  }
  fputc(0, outfile); outlen++;   /* padding byte */
  fputc(c0, outfile); outlen++;   /* initial value */
  while (len > 0) {
    fread(sbuf, sizeof(char), ibuf, infile); len -= ibuf;
    obuf = 0;
    for (i=0; i<ibuf; i++) {
      c = sbuf[i];
      delta = c - c0;
      if (mode == 1) {  /* fibonacci delta */
        n = qtab[256+delta];   /* output nibble */
        c0 += fibtab[n];
      } else {   /* exponential delta */
        n = dtab[128+c0][128+c];
        c0 += exptab[n];
      }
      if (i & 1)
        cbuf[obuf++] = o | n;
      else
        o = n<<4;
    }
    fwrite(cbuf, sizeof(char), obuf, outfile); outlen += obuf;
    ibuf = SBUFSIZE;   /* same size for all packets but the first */
    if (verbose) {
      printf("\r%s track: %d output bytes to go. ",info, len/2);
      fflush(stdout);
    }
    if (ctrl_c) {
      printf("\n*** BREAK - output sample is incomplete!\n");
      break;
    }
  }
  signal(SIGINT, SIG_DFL);  /* restore normal handling */
  
  return outlen;
}

long unpack_body(long len, char *info)
/* Unpacks a complete mono sample *or* one stereo track, */
/* return value is the number of bytes written. */
{
  long ibuf, obuf, i, outlen = 0;
  int delta;
  unsigned char c;  /* input bytes */
  signed char y;    /* output sample bytes */

  signal(SIGINT, *oops);  /* intercept user break (Ctrl-C) */
  fgetc(infile); len--;       /* drop the zero byte */
  y = fgetc(infile); len--;   /* get initial value */
  if (y || stereo) {
    fputc(y, outfile); outlen++;  /* don't copy a zero byte at the start */
    /* of a mono track, as it would only provoke a zero padding byte */
    /* at the end of the track, anyway */
  }
  ibuf = len % (SBUFSIZE/2);       /* size of first packet */
  while (len > 0) {
    fread(cbuf, sizeof(char), ibuf, infile); len -= ibuf;
    obuf = 0;
    for (i=0; i<ibuf; i++) {
      c = cbuf[i];
      if (mode == 1) {  /* delta fibonacci */
        sbuf[obuf++] = y += fibtab[c>>4];
        sbuf[obuf++] = y += fibtab[c & 0xf];
      } else {   /* delta exponential */
        sbuf[obuf++] = y += exptab[c>>4];
        sbuf[obuf++] = y += exptab[c & 0xf];
      }
    }
    fwrite(sbuf, sizeof(char), obuf, outfile); outlen += obuf;
    ibuf = SBUFSIZE/2;   /* same size for all packets but the first */
    if (verbose) {
      printf("\r%s track: %d input bytes to go. ",info, len);
      fflush(stdout);
    }
    if (ctrl_c) {
      printf("\n*** BREAK - output sample is incomplete!\n");
      break;
    }
  }
  signal(SIGINT, SIG_DFL);  /* restore normal handling */
  
  return outlen;
}

ULONG getlong()
/* returns 0 at EOF, not so brilliant, but sufficient for our purpose */
{
  ULONG l=0;
  int i, c;
  for (i=0; i<4; i++)
    l = (l<<8) | (c = fgetc(infile));
  if (c != EOF)
    return l;
  else
    return 0L;
}

void putlong(ULONG l)
{
  int i, byte[4];
  for (i=0; i<4; i++) {
    byte[3-i] = l & 0xff; l >>= 8;
  }
  for (i=0; i<4; i++)
    fputc(byte[i], outfile);
}

UWORD getword()
{
  UWORD w;
  w = fgetc(infile) << 8;
  return w | fgetc(infile);
}

void putword(UWORD w)
{
  fputc(w >> 8, outfile);
  fputc(w & 0xff, outfile);
}

void copy_chunk(int verbose)
/* copy an IFF chunk to the output file (its ID has already been copied) */
{
  ULONG l;
  UBYTE c;
  int i=0;

  putlong(l = getlong());
  if (verbose>0)
    while (l--) {
      fputc(c = fgetc(infile), outfile);
      if (i++ < verbose)
        if ((c & 0x7f)<' ') putchar('.'); else putchar(c);
    }
  else if (verbose<0)
    while (l--) {
      fputc(c = fgetc(infile), outfile);
      if (i++ < -verbose) {
        printf("%02x",c); if ((i & 3) == 0) putchar(' ');
      }
    }
  else
    while (l--)
      fputc(fgetc(infile), outfile);
}

void handle_iff()
/* The input file has been recognized as valid IFF 8SVX, so copy it */
{
  ULONG l, ifflen, vhdlen, oldbody, newbody;
  fpos_t pos1, pos2, pos3;  /* markers for values calculated later */
  int i, n, done = 0, packed = 0;
  char s1[5];

  putlong(getlong());  /* "FORM" */
  fgetpos(outfile, &pos1);  /* pos1: for ifflen */
  putlong(ifflen = getlong());
  putlong(getlong());  /* "8SVX" */
  while (!done && (l = getlong())) {
    putlong(l);
    if (verbose) {
      strncpy(s1, (char *)&l, 4); s1[4] = '\0'; printf("%s: ",s1);
    }
    if (l == ID_VHDR) {
      fgetpos(outfile, &pos2);
      pos2 += 19;       /* pos2: sCompression byte in the VHDR */
      copy_chunk(-24*verbose); if (verbose) putchar('\n');
    } else if (l == ID_CHAN) {
      putlong(n = getlong());
      putlong(l = getlong());
      if (verbose) printf("%08x\n", l);
      stereo = (l == 6);
      for (i=4; i<n; i++)
        fputc(fgetc(infile), outfile);   /* should be an empty loop */
    } else if (l == ID_BODY) {
      fgetpos(outfile, &pos3);  /* pos3: for size of BODY */
      fsetpos(outfile, &pos2);  /* back to the VHDR */
      if (packed = fgetc(outfile)) {
        if (mode != 0) {
          printf("Error: sample is already compressed (%s delta).\n",
            modename[mode-1]);
          exit(10);
        } else {
          mode = packed;
          fsetpos(outfile, &pos3);
          putlong(oldbody = getlong());
          if (stereo) {
            newbody = unpack_body(oldbody/2, "left"); if (!ctrl_c)
            newbody += unpack_body(oldbody/2, "right");
          } else
            newbody = unpack_body(oldbody, "mono");
          if (newbody & 1L) {  /* odd chunk size, fix it */
            fputc(0, outfile); newbody++;
          }
          mode = 0;
        }
      } else if (mode == 0) {
        printf("Error: sample isn't compressed at all.\n");
        exit(10);
      } else {
        fsetpos(outfile, &pos3);
        putlong(oldbody = getlong());
        if (stereo) {
          newbody = pack_body(oldbody/2, "left"); if (!ctrl_c)
          newbody += pack_body(oldbody/2, "right");
        } else
          newbody = pack_body(oldbody, "mono");
        if (newbody & 1L) {  /* odd chunk size, fix it */
          fputc(0x88, outfile); newbody++;    /* 0x88 is two zero deltas */
        }
      }
      printf("\rBODY size is now %d bytes.\e[K\n", newbody);   /* ClrEOL */
      done = 1;   /* process no chunks after BODY */
    } else {
      copy_chunk(60*verbose); if (verbose) putchar('\n');
    }
  }
  /* now adjust the sizes: */
  fsetpos(outfile, &pos1); putlong(ifflen + newbody - oldbody);
  fsetpos(outfile, &pos3); putlong(newbody);
  /* and set the sCompression byte: */
  fsetpos(outfile, &pos2); fputc(mode, outfile);
}


void help(char *s)
/* print a help text and nag about illegal parameter <s> */
{
  if (s) fprintf(stderr,"Illegal parameter '%s'.\n", s);
  fprintf(stderr,"Usage: wavepak [options] <infile> <outfile>\n", s);
  fprintf(stderr,"Where options are:\n");
  fprintf(stderr," -v    verbose: progress indicator & info about chunks\n");
  fprintf(stderr," -f    compress using standard Fibonacci Delta\n");
  fprintf(stderr," -e    compress using 1996 Exponential Delta\n");
  fprintf(stderr,"Default operation is *un*compress.\n");
}

