/* This program converts SDX (SDS eXchange Format) files to
Yamaha TX16W .W wave files. It runs as a pipe, so to use it:

sdx2tx < sdxfile.sdx > yamfile.w01

This utilises documentation from alt.uib.no in /pub/midi/sds and
from ftp-ls7.informatik.uni-dortmund.de in /pub/tx16w.

Copyright Mark Lakata (c) 1994
*/
#include <stdio.h>
#define SoftwareVersion "1.00"
#define SDXVersion 1
#define PCMPacking 0

struct DataHeader_ {
  long
    Packing,
    MIDIChannel,
    SampleNumber,
    SampleFormat,
    SampleRate,
    SampleLength,
    LoopStart,
    LoopEnd,
    LoopType,
    Reserved;
} ;

struct WaveHeader_ {
  unsigned char
    filetype[6], /* = "LM8953", */
    nulls[10],
    dummy_aeg[6],    /* space for the AEG (never mind this) */
    format,          /* 0x49 = looped, 0xC9 = non-looped */
    sample_rate,     /* 1 = 33 kHz, 2 = 50 kHz, 3 = 16 kHz */
    atc_length[3],   /* I'll get to this... */
    rpt_length[3],
    unused[2];       /* set these to null, to be on the safe side */
} ;

static char magic1[4] = {0, 0x06, 0x10, 0xF6};
static char magic2[4] = {0, 0x52, 0x00, 0x52};

long convert(unsigned char *number)
{
  return
    number[0] +
      number[1]*0x100L +
	number[2]*0x10000L +
	  number[3]*0x1000000L;
}


int main(int argc,char **argv)
{
  char PreHeaderType[5],FileName[256],TextNote[256];
  struct DataHeader_ DH;
  struct WaveHeader_ WH;
  long LoopLength,AttackLength,BytesWritten,w1,w2,i,j,Version;
  char buffer[24];

  fprintf(stderr,
	  "sdx2tx V%s, by Mark Lakata (lakata@physics.berkeley.edu)\n",
	  SoftwareVersion);

/* read in SDX  ------------------------------------- */
  fgets(PreHeaderType,5,stdin);
  if (strcmp(PreHeaderType,"SDX:")) {
    fprintf(stderr,"Input stream is not SDX format! Got '%s'\n",PreHeaderType);
    return 1;
  }
  
  i=0;
  while ( (FileName[i] = getchar()) != 26) i++;
  FileName[i] = 0;
  fprintf(stderr," Sample: %s\n",FileName);
  
  if ((Version = getchar()) != SDXVersion) {
    fprintf(stderr,"Can not convert this Version (%d)\n",Version);
    return 1;
  }

  j = getchar();
  i = 0;
  while (i < j) TextNote[i++]=getchar();
  TextNote[i] = 0;
  if (j) fprintf(stderr," Description: %s\n",TextNote);

  fread((char *)&buffer,1,23,stdin);

  DH.Packing      = buffer[0];
  DH.MIDIChannel  = buffer[1];
  DH.SampleNumber = buffer[2]+256*buffer[3];
  DH.SampleFormat = buffer[4];
  DH.SampleRate   = convert(&buffer[5]);
  DH.SampleLength = convert(&buffer[9]);
  DH.LoopStart    = convert(&buffer[13]);
  DH.LoopEnd      = convert(&buffer[17]);
  DH.LoopType     = buffer[21];
  DH.Reserved     = buffer[22];

  if (DH.Packing != PCMPacking) {
    fprintf(stderr,"Unsupported Packing  (%d)\n",DH.Packing);
    return 1;
  }
/*  fprintf(stderr,"MIDI         : %d",DH.MIDIChannel); */
/*  fprintf(stderr,"Sample Number: %d\n",DH.SampleNumber); */
  fprintf(stderr," Sampling Rate: %d samples/sec  ",DH.SampleRate);
  fprintf(stderr,"Sample Length: %d samples\n",DH.SampleLength);
/*  fprintf(stderr,"Reserved     : %d\n",DH.Reserved); */

/* write out yamaha wave -------------------------------------------- */

  strncpy(WH.filetype,"LM8953",6);
  for (i=0;i<10;i++) WH.nulls[i]=0;
  for (i=0;i<6;i++)  WH.dummy_aeg[i]=0;
  for (i=0;i<2;i++)  WH.unused[i]=0;
  for (i=0;i<2;i++)  WH.dummy_aeg[i] = 0;
  for (i=2;i<6;i++)  WH.dummy_aeg[i] = 0x7F;
  
  switch (DH.LoopType) {
  case 1:    WH.format = 0x49;
    fprintf(stderr,"Warning: Can't do alternating direction loops.\n");
  case 0:    WH.format = 0x49;
    fprintf(stderr," Loop: %d to %d  ",DH.LoopStart,DH.LoopEnd);
    break;
  case 0x7F: WH.format = 0xC9;
    break;
  };
  
  /* the actual sample rate is not that important ! */  
  if (DH.SampleRate < 24000)      WH.sample_rate = 3;
  else if (DH.SampleRate < 41000) WH.sample_rate = 1;
  else                            WH.sample_rate = 2;
  
  if (DH.SampleLength >= 0x20000) {
    fprintf(stderr,"Sound too large for TX16W. Truncating, Loop Off\n");
    DH.SampleLength    = 0x20000;
    AttackLength       = 0x10000;
    LoopLength         = 0x10000;
    WH.format = 0xC9;
  }
  else {
    AttackLength                           = DH.LoopStart;
    if (AttackLength < 0x40)  AttackLength = 0x40;
    LoopLength                             = DH.LoopEnd-DH.LoopStart+1;
    if (LoopLength <0x40) LoopLength       = 0x40;
  }

  WH.atc_length[0] = 0xFF & AttackLength;
  WH.atc_length[1] = 0xFF & (AttackLength >> 8);
  WH.atc_length[2] = (0x01 & (AttackLength >> 16)) +
    magic1[WH.sample_rate];

  WH.rpt_length[0] = 0xFF & LoopLength;
  WH.rpt_length[1] = 0xFF & (LoopLength >> 8);
  WH.rpt_length[2] = (0x01 & (LoopLength >> 16)) +
    magic2[WH.sample_rate];

  fwrite(&WH,1,32,stdout);
  BytesWritten = 32;

/* pad attack  */
  for (j = DH.LoopStart; j < 0x40; j+=2) {
    putchar(0);
    putchar(0);
    putchar(0);
    BytesWritten+=3;
  }

/* assuming: sds unsigned? yamaha signed? */
  switch (DH.SampleFormat) {
  case 15:
    fprintf(stderr," 16 Bit Data\n\n");
    for (i=0;i<DH.SampleLength;i+=2) {
      w1 =      (unsigned long)getchar();
      w1 += 256*(unsigned long)getchar();
      if (w1>=0x8000) w1 -= 0x8000;
      else w1 += 0x8000;
      w1 = w1 >> 4;
      if (i+1==DH.SampleLength)
	w2 = 0;
      else {
	w2 =      (unsigned long)getchar();
	w2 += 256*(unsigned long)getchar();
	if (w2>=0x8000) w2 -= 0x8000;
	else w2 += 0x8000;
	w2 = w2 >> 4;
      }
      putchar((w1 >> 4) & 0xFF);
      putchar((((w1 & 0x0F) << 4) | (w2 & 0x0F)) & 0xFF);
      putchar((w2 >> 4) & 0xFF);
      BytesWritten += 3;
    }
    break;
  case 8:
    fprintf(stderr," 8 Bit Data\n\n");
    for (i=0;i<DH.SampleLength;i+= 2) {
      w1=getchar();
      if (w1>= 0x80) w1 -= 0x80;
      else w1 +=0x80;
      w2=getchar();
      if (w2>= 0x80) w2 -= 0x80;
      else w2 +=0x80;
      putchar(w1);
      putchar(0);
      putchar(w2);
      BytesWritten += 3;
    }
    break;
  default:
    fprintf(stderr,"Not implemented for %d bits\n",DH.SampleFormat);
    return 1;
  }

  /* pad loop */
  for (j = DH.LoopEnd-DH.LoopStart+1; j < 0x40; j+=2 ) {
    putchar(0);
    putchar(0);
    putchar(0);
    BytesWritten+=3;
  }

  /* Fill up to 256 byte blocks; the TX16W seems to like that */
  while ((BytesWritten % 0x100) != 0) {
    putchar(0);
    BytesWritten++;
  }
  
  return 0; 
}
