/* This program converts Kurzweil files (*.krz) files to
Yamaha TX16W .W wave files.

Usage:

krz2tx filename.krz

It outputs one TX16W .W?? file for each sample found in the .krz file.

K2000 files can be found at cs.uwp.edu  /pub/music/kurzweil/sounds

TX16W info can be found at ftp-ls7.informatik.uni-dortmund.de in /pub/tx16w.

Copyright Mark Lakata (c) 1994
Absolutely freeware, do whatever the hell you want with it.
*/

#define SoftwareVersion ".93 August 14, 1994"
#define Verbose 1

#include <stdio.h>
#ifndef SEEK_SET
#define SEEK_SET 0
#endif
/* this macro rounds UP to the nearest even 4 byte boundary */
#define evenlongword(x) (((x)%4)?((x)-((x)%4)+4):(x))

int PrepareDOSName();

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};

unsigned long motorola4(x)
unsigned long x;
{
  unsigned char *b;
 
  b=(unsigned char *)&x;
  return b[0]*0x01000000 + 
         b[1]*0x00010000 +
         b[2]*0x00000100 +
         b[3]*0x00000001;

}

unsigned short motorola2(x)
unsigned short x;
{
  unsigned char *b;
 
  b=(unsigned char *)&x;
  return b[0]*0x0100 +
         b[1]*0x0001;

}

main(argc,argv)
int argc;
char *argv[];
{

  char header[4];
  unsigned long header_length;
  unsigned long header_stuff[6];
  unsigned long object_type;
  unsigned short object_num;  
  unsigned short object_len;
  unsigned short name_len;
  char name[80];
  unsigned long rest,rest1,rest2;
  unsigned long *longwordbuffer;
  char crap[4];
  int data_len,i;
  int maxallocated = 0x100;
  char FileName[256],tmpfile[256];
  struct WaveHeader_ WH;
  long LoopLength,AttackLength,BytesWritten,w1,w2,j,Version,savepos,
       SampleLength,TXLoopLength,TXAttackLength;
  char extension[5];
  int Status,filecount=1;

  FILE *in,*out;


  printf("Kurzweil K2000 sample stealer to TX16W format (%s)\n",
         SoftwareVersion);
  printf(" by Mark Lakata <lakata@physics.berkeley.edu>\n\n"); 
  if (argc < 2) {
    printf("Usage: krz2tx filename.krz [dest_dir]\n\n");
    exit(1);
  }

  if ((in = fopen(argv[1],"r")) == NULL) {
    fprintf(stderr,"Can't open file %s\n",argv[1]);
    exit(1);
  }

/* beginning decoding .krz file */

  fread(header,4,1,in);
  if (strncmp(header,"PRAM",4)!= 0 ) {
    fprintf(stderr,"Input file is not a Kurzweil K2000 file\n");
    exit(1);
  }

  fread(&header_length,1,4,in);
  header_length = motorola4(header_length);

/*
  if (Verbose) printf("header length = %8.8lx\n",header_length);
*/

/* this stuff is junk, for all I care about, but let's read it anyways */
  fread(header_stuff,6,4,in);

  longwordbuffer = (unsigned long *) malloc(maxallocated*4);

  while(ftell(in) < header_length) {

    fread(&object_type,1,4,in);
    object_type = motorola4(object_type);

/* objects all ways start with FFFF.  If there has been a mistake, then
stop reading objects! */
    if (object_type<0xFFFF0000) break;

    fread(&object_num,1,2,in);
    object_num = motorola2(object_num);

/* this is the length of the object, measured in bytes, but counting from
the "object_type" offset.  The object is padded so that the next object
lies on a even 4-byte boundary */
    fread(&object_len,1,2,in);
    object_len = motorola2(object_len);

/* the name length is measured in bytes, counting from the "name_len" offset */
    fread(&name_len,1,2,in);
    name_len = motorola2(name_len);

    if (name_len > 2)
/* the name is null terminated, so sometimes strlen(name) < (name_len-2).  The
name is padded to even 2-byte boundaries (why?). */
      fread(name,name_len-2,1,in);
    else {
      fprintf(stderr,"name is too short! = %d\n",name_len);
      name[0]=0;
    }
    if (Verbose) printf("Object #%8.8lx %4.4x length=%l4.4x '%s'\n",
                 object_type,object_num,object_len,name);

/* evenlongword(object_len) is the number of bytes in this object */
/* 'rest' is the number of bytes to the next object */
/* data_len is the maximum number of 4byte longwords that could fit in
this object; the remaining (rest2) bytes are skipped */
    rest = evenlongword(object_len)-4-name_len;
    rest2 = rest%4;
    rest1 = rest-rest2;
    data_len = rest1/4;
    if (data_len>maxallocated) {
      longwordbuffer = (unsigned long *)realloc(longwordbuffer,data_len*4);
      maxallocated = data_len;
    }

/* ok, read in the data as long words */
    if (data_len>0) {
      fread(longwordbuffer,data_len,4,in);
      for(i=0;i<data_len;i++) longwordbuffer[i] = motorola4(longwordbuffer[i]);
    }
    if (rest2 > 0) fread(crap,rest2,1,in);

/* this seems to be the indicator of a "sample" structure.  The structure
looks something like this:
longword   description
---------------------------
0          0x00010000
1          0x00080000
2          ?
3.3        byte3= original MIDI note #, 0x3C = 60 = middle C
3.[210]   ?? (sample rate?)
           700A00 700E00 F00000 F00000 700000
4          something to do with the sample length
5          sample start
6          sample start, raw?
7          loop start
8          sample end
9         ?? 0x00080006 0x00080012  (mono /stereo ??)
10        ?? 0x000084DE 0x000084DD  0x00007CCC 0x000063E7 0x00005893
(if [9] = 00080006)
11        ?? 0x00009999
12        ?? 0x00000000
13        ?? 0xF9C00000
end
(if [9] = 00080012)
11        ?? 0xFFFF0001
12        ?? 0x00000000
13        ?? 0xF9C00000
14        ?? 0xFFFF0001
15        ?? 0x00000000
16        ?? 0xF9C00000
end
*/

    if (longwordbuffer[0] == 0x00010000) {
      if (Verbose) {
/*
        printf("Sample FOUND!\n");
        printf("OK = %2x  %8.8lx %8.8lx %8.8lx\n",
          longwordbuffer[3]/0x01000000,
          longwordbuffer[6],longwordbuffer[7],longwordbuffer[8]);
/*
/*
        for(i=0;i<data_len;i++)  printf("%8.8lx  ",longwordbuffer[i]);
        printf("\n\n");
*/
      }
/* save current position in file */
      savepos = ftell(in);
/* go to the sample data */
      fseek(in,header_length+2*longwordbuffer[6],SEEK_SET);

/* create a filename from the sample name, and make it DOS compatible */ 
      if (0==PrepareDOSName(name,FileName)) {
        fprintf(stderr,"something happened to the filename!\n");
        fprintf(stderr,"in=%s out=%s\n",name,FileName);
        exit(1);
      }
/* if there is a 2nd command argument, use it as the directory name */
      if (argc == 3) {
        strcpy(tmpfile,FileName);
        strcpy(FileName,argv[2]);
        strcat(FileName,tmpfile);
      }
      sprintf(extension,".W%2.2d",filecount++);
      strcat(FileName,extension);

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

      if ((out = fopen(FileName,"wb")) == NULL) {
        fprintf(stderr,"Can't open file %s\n",FileName);
        exit(1);
      }

      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;

      if (longwordbuffer[7] != longwordbuffer[8]) {
/* set loop on */
        WH.format = 0x49;
        if (Verbose) printf(" Loop: %d to %d  ",longwordbuffer[7],
                longwordbuffer[8]);
      }
      else
/* loop off */
        WH.format = 0xC9;
  
/* the actual sample rate is not that important, because the tx16w and
K2000 probably don't have the same rates */  
/* I should figure it out eventually */
/*
      if (DH.SampleRate < 24000)      WH.sample_rate = 3;
      else if (DH.SampleRate < 41000) WH.sample_rate = 1;
      else                            WH.sample_rate = 2;
*/
      WH.sample_rate = 1;   /* 33000 Hz */

      AttackLength       = longwordbuffer[7]-longwordbuffer[6];
      LoopLength         = longwordbuffer[8]-longwordbuffer[7]+1;
      SampleLength       = longwordbuffer[8]-longwordbuffer[6]+1;
    

      if (Verbose) printf("Sample: attack=%8.8lx  loop=%8.8lx  ",
          AttackLength,LoopLength);

      if (SampleLength > 0x40000 ) {
	fprintf(stderr,"Entire sample is too long: %s (%s), TRUNCATING...\n",
		name,FileName);
        AttackLength = 0x20000;
        LoopLength   = 0x20000;
        SampleLength = 0x40000;
        WH.format = 0xC9;
      }
      else if (AttackLength > 0x20000 || LoopLength > 0x20000) {
	fprintf(stderr,"Attack or Loop is too long: %s (%s), TRUNCATING...\n",
		name,FileName);
        AttackLength = 0x20000;
        LoopLength   = SampleLength-AttackLength;
        WH.format = 0xC9;
      }

/* tx16w is fussy about having at least 64 bytes in each part of the wave */
      TXAttackLength = (AttackLength < 0x40)?0x40:AttackLength;
      TXLoopLength   = (LoopLength   < 0x40)?0x40:LoopLength;

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

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

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

/* pad attack  */
 
      for (j = AttackLength;j < 0x40; j+=2) {
        putc(0,out);
        putc(0,out);
        putc(0,out);
        BytesWritten+=3;
      }

      for (i=0;i<SampleLength;i+=2) {
        w1 =  0x100*(unsigned long)(Status = getc(in));
        w1 +=       (unsigned long)          getc(in);
        w1 = w1 >> 4;
        if ( Status == EOF) {
          w1 = 0;
          w2 = 0;
        }        
        else {
          if (i+1==SampleLength)
            w2 = 0;
          else {
            w2 =  0x100*(unsigned long)getc(in);  
            w2 +=       (unsigned long)getc(in);
            w2 = w2 >> 4;
          }
        }
        putc((w1 >> 4) & 0xFF,out);
        putc((((w1 & 0x0F) << 4) | (w2 & 0x0F)) & 0xFF,out);
        putc((w2 >> 4) & 0xFF,out);
        BytesWritten += 3;
      }

  /* pad loop */

      for (j = LoopLength; j < 0x40; j+=2 ) {
        putc(0,out);
        putc(0,out);
        putc(0,out);
        BytesWritten+=3;
      }

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

      fclose(out);
      if (Verbose) printf("in file %s\n",FileName);
/* go back to where we were, in header */
      fseek(in,savepos,SEEK_SET);
    }
  }

  free(longwordbuffer);


  if (header_length == ftell(in)) 
    printf("Perfectly ");

  printf("Done.\n");

  fclose(in);
}


/*

examples of object_types and object_numbers ...

samples:
FFA4 98d6  hit 1
FFA4 98e2  hit 2
FFB0 992d  j b stab
FFA0 9990  shinoboe
FFB0 98c8  steel drum c4
FFB0 98c9  steel drum f4
FFAC 98ca  steel drum c4h
FFAC 98cb  steel drum f4h


non-samples (voices, FX, ??):
FECC 9590  drum loop3 (not used??)
FFBC 7144  chorus slap (not used)
FFB8 7145  prophect chorus
FEF4 9190  hits
FD50 94e2  hits -L1
FF50 9590  shinoboe
FED8 9190  shinoboe

*/ 


int PrepareDOSName(in,out)
char *in,*out;
{
  static char badchars[6]="|/\\. ",vowels[7]="AEIOU-";
  int i,j,len,outlen;

  j=0;
  len = strlen(in);
  outlen=0;
/* first clean out bad characters */
  for(i=0;i<len;i++)
    if (strchr(badchars,in[i]) == NULL)
      out[outlen++]=toupper(in[i]);
  out[outlen]=0;
/*
  if (Verbose) printf(" -[Strip %s]-> '%s'",badchars,out);
*/

/* remove vowels (strip all vowels after the first letter) */
  j=0;
  for(i=outlen-1;i >=1 && outlen > 8; i--) {
    if (strchr(vowels,out[i]) != NULL) {
      for(j=i+1;j<=outlen;j++)
	out[j-1]=out[j];
      outlen--;
/*
    if (Verbose) printf(" -[Strip %s]-> %s ",vowels,out);
*/
    }
  }
/* if it still too long, just lop it off */
  if (outlen>8) {
    out[8] = 0;
    outlen = 8;
/*
    if (Verbose) printf("-[Lop]-> %s\n",out);
*/
  }
  
  return outlen;
}
