/* makewav.c version 3.1*/
/*

   Written by Bob Colbert
          
   released to the public domain
          
   The author assumes no responsibility for any damage caused by the use or
   posession of this program.  USE AT YOUR OWN RISK!!!
          
*/

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

#define WAV_MODE 0
#define RAW_MODE 1

FILE *binfile, *wavfile;
unsigned char buffer[260], pg_bank_list[24], empty_page[24], filename[80];
float flength,flength2;
int i,j,k,startlo,starthi,control,dflag,kflag,vflag,filecount,create_mode;

unsigned char zero_bit[2][80]={{0x06,0x06,0x80,0xfc,0xfc,'\0'},
  {0x06,0x06,0x06,0x06,0x06,0xfc,0xfc,0xfc,0xfc,0xfc,'\0'}};
unsigned char one_bit[2][80]={{0x06,0x06,0x06,0x06,0x80,0xfc,0xfc,0xfc,0xfc,'\0'},
  {0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,'\0'}};
unsigned char fast_zero_bit[2][10]={{0x06,0x80,0xfc,'\0'},
  {0x06,0x06,0x06,0xfc,0xfc,0xfc,'\0'}};
unsigned char fast_one_bit[2][20]={{0x06,0x06,0x80,0xfc,0xfc,'\0'},
  {0x06,0x06,0x06,0x06,0x06,0xfc,0xfc,0xfc,0xfc,0xfc,'\0'}};
unsigned char header[2][80]={{'R','I','F','F',0x80,0x80,0x80,0x80,'W','A','V','E','f','m','t',' ',0x10,0,0,0,1,0,1,0,0x22,0x56,0,0,0x22,0x56,0,0,1,0,8,0,'d','a','t','a',0,0,0,0,'\0' },
  {'R','I','F','F',0x80,0x80,0x80,0x80,'W','A','V','E','f','m','t',' ',0x10,0,0,0,1,0,1,0,0x44,0xac,0,0,0x44,0xac,0,0,1,0,8,0,'d','a','t','a',0,0,0,0,'\0' }};

void put_zero_bit(void)
{
        fwrite(zero_bit[kflag],strlen(zero_bit[kflag]),1,wavfile);
        flength = flength + strlen(zero_bit[kflag]);
}

void put_one_bit(void)
{
        fwrite(one_bit[kflag],strlen(one_bit[kflag]),1,wavfile);        
        flength = flength + strlen(one_bit[kflag]);
}

void put_byte(unsigned char curr_byte)
{
    fputc(curr_byte,wavfile);
    flength = flength + 1;
}

void process_byte(unsigned char curr_byte,int mode)
{
    int p;

    if (mode == WAV_MODE)
        for(p=0;p<8;p++) {
                if (curr_byte > 127)
                    put_one_bit();
                else
                    put_zero_bit();
                curr_byte = curr_byte<<1;
        }
    else
        put_byte(curr_byte);
}

void get_page(unsigned int page, int display)
{
     if (display)
         if (!page || dflag)
             printf("File page $%0.2x",(unsigned char)page);
         else
             printf("\b\b%0.2d",page);
     fseek(binfile,(long) page * 256,SEEK_SET);
     fread(buffer,256,1,binfile);
}

unsigned char setvol(unsigned int amplitude, int volume)
{
    unsigned int tempval;

    if (amplitude > 128)
        tempval = floor(((amplitude - 128) * volume)/10 + 128);
    else
        tempval = floor(128 - ((128 - amplitude) * volume/10));
    return tempval;
}

main(int argc, unsigned char *argv[])
{
  int c,i,j,file_page_count,game_page_count,pcount,sflag,mflag,fflag,scflag,pflag;
  unsigned int length_lword[4],in_byte, sum, pg_bank_byte, page, init_bank,volume;
  float gaplength,hlength,hmulti;
  int creat_mode;
  long fsize;
  unsigned int multi,multi_byte,control_byte, speed_low, speed_high = 0x00;

  filecount = 0;
  control = 0;
  sflag = 0;
  scflag = 0;
  kflag = 0;
  mflag = 0;
  fflag = 0;
  dflag = 0;
  pflag = 0;
  vflag = 0;
  multi = 0;
  hmulti = 1;
  hlength = 1575;
  create_mode = WAV_MODE;

/* If fast mode, double the length of the header tone */
/* and copy fast waveforms to zero_bit and one_bit    */

  while (--argc > 0 && (*++argv)[0] == '-') {
    c = *++argv[0];
    switch (c) {
      case 'd':
        dflag = 1;
        break;
      case 'p':
        pflag = 1;
        break;
      case 'f':
        fflag = 1;
        hlength = 2756;
        break;
      case 'c':
        printf("argv = %s\n",*argv);
        if (strlen(*argv) != 3) {
          printf("Illegal control byte value!\n");
          exit(1);
        }
        if (sscanf(*argv,"c%2x",&control) == EOF) {
          printf("Illegal control byte value!\n");
          exit(1);
        }
        printf("Control byte set to %0.2x\n",control);
        break;
      case 'h':
        if (sscanf(*argv,"h%f",&hmulti) == EOF) {
          printf("Illegal header length!\n");
          exit(1);
        }
        if (hmulti > 10 || hmulti <=0) {
          printf("Bad header length!\n");
          exit(1);
        }
        break;
      case 's':
        if (strlen(*argv) !=5) {
          printf("Illegal start value!\n");
          exit(1);
        }
        if (sscanf(*argv,"s%2x%2x",&starthi,&startlo) == EOF) {
          printf("Illegal start value!\n");
          exit(1);
        }
        sflag = 1;
        break;
      case 'k':
        kflag = 1;
        break;
      case 'm':
        if (strlen(*argv) != 3) {
          printf("Illegal multi-byte value!\n");
          exit(1);
        }
        if (sscanf(*argv,"m%2x",&multi) == EOF) {
          printf("Illegal multi-byte value!\n");
          exit(1);
        }
        printf("Multi-load byte is $%0.2x\n",multi);
        mflag = 1;
        break;
      case 'r':
        create_mode = RAW_MODE;
        break;
      case 'v':
        if ((strlen(*argv) == 1) || (strlen(*argv)>3)) {
          printf("Illegal volume value, must be 1-10\n");
          exit(1);
        }
        vflag = 1;
        if (sscanf(*argv,"v%2i",&volume) == EOF) {
          printf("Illegal volume value, must be 1-10\n");
          exit(1);
        }
        if ((((int)volume < 1)) || ((int)volume > 10)) {
          printf("Illegal volume value, must be 1-10\n");
          exit(1);
        }
        break;
      default :
        printf("Unknown flag %c\n",c);
        exit(1);
        break;
      }
  }

  if (fflag) {
    strcpy(zero_bit[kflag],fast_zero_bit[kflag]);
    strcpy(one_bit[kflag],fast_one_bit[kflag]);
  }

  if (vflag) {
    for (i=0;i<strlen(zero_bit[kflag]);i++)
        zero_bit[kflag][i]=setvol(zero_bit[kflag][i],volume);
    for (i=0;i<strlen(one_bit[kflag]);i++)
        one_bit[kflag][i]=setvol(one_bit[kflag][i],volume);
  }

  gaplength = hlength;
  hlength = floor(hlength * hmulti);

  if (argc > 1) {
   if ((wavfile = fopen(argv[1],"wb")) == NULL) {
     printf("ERROR: unable to create wavfile %s\n",argv[2]);
     exit(1);
   }
   strcpy(filename,strtok(argv[0],"+"));
   while (strcmp(filename,"")) {
    page = 0;
    if ((binfile = fopen(filename,"rb")) == NULL) {
      printf("ERROR: unable to open .bin file %s\n",argv[1]);
      exit(1);
    }

    fseek(binfile,0,SEEK_END);
    fsize = ftell(binfile);
    multi_byte = 0;
    if (fsize == 2048) {
      if (!control) control_byte = 0x1d;
      speed_low = 0x60;
      speed_high = 0x01;
      file_page_count = 8;
      init_bank = 2;
    } else if (fsize == 4096) {
      if (!control) control_byte = 0x1d;
      speed_low = 0xc1;
      speed_high = 0x01;
      file_page_count = 16;
      init_bank = 1;
    } else if (fsize >= 6144) {
      if (!control) control_byte = 0x0d;
      speed_low = 0x42;
      speed_high = 0x02;
      file_page_count = 24;
      init_bank = 0;
      if (fsize >= 8448) {
        get_page(32,0);
        if (!sflag) {
          startlo=buffer[0];
          starthi=buffer[1];
        }
        sflag = 1;
        if (!control) control = buffer[2];
        file_page_count = buffer[3];
        scflag = 1;
        for (i=0;i<0x18;i++)
          pg_bank_list[i] = buffer[0x10+i];
        multi_byte = buffer[5];
      }     
    } else {
       printf("ERROR!  Source .bin file must be 2048, 4096, 6144, or 8448 bytes\n");
       exit(1);
    }


    game_page_count = 0;
    for(i=0;i<file_page_count;i++) {
        get_page(i,0);
        empty_page[i] = 1;
        for (j=0;j<255;j++)
          if (buffer[j] != buffer[j+1])
            empty_page[i] = 0;
        if (!empty_page[i]) {
          game_page_count++;
        }/* else
          printf("page %d is empty\n",i); */
    }
    if ((pflag))
        game_page_count = file_page_count;

/* Write .wav header */
    if ((filecount == 0) && create_mode == 0)
      for(k=0;k<44;k++)
        fputc(header[kflag][k],wavfile);

/* Write game header tone, just a series of alternating zero and one bits */
/*
    for(k=1;k<hlength;k++) {
      put_one_bit();
      put_zero_bit();
    }
*/

    for(k=1;k<(hlength/4);k++) {
      process_byte(0x55,create_mode);
    }

/* Two zero bits in a row indicate the beginning of the data */
       
/*    put_zero_bit(); */

    process_byte(0x54,create_mode);

    while (page < file_page_count) {
      if (page == 0) {
                
/* Get last page of game file - it contains the start address of the code */

        get_page(file_page_count-1,0);
                
/* The first two bytes of data indicate the beginning address of the code */

        if (!sflag) {
          startlo = buffer[0xfc];
          starthi = buffer[0xfd];
        }
		
        process_byte(startlo,create_mode);
        process_byte(starthi,create_mode);
        if (fsize == 2048 && buffer[0xfd] < 0xf8 && (!control))
          control_byte = 0x09;
                
/* This is the control byte, it determines the bankswitching mode of the SC

               Bits                Function
               ------              --------
               D7-D5               Write Pulse Delay (Set to 0)
               D4-D2               RAM/ROM Configuration
               
                                   Value     $f000     $f800
                                   -----     -----     -----
                                    000        3        ROM
                                    001        1        ROM
                                    010        3         1
                                    011        1         3
                                    100        3        ROM
                                    101        2        ROM
                                    110        3         2
                                    111        2         3
*/

        if (control)
          control_byte = control;

        process_byte(control_byte,create_mode);
                
/* Number of pages to load */                

        process_byte(game_page_count,create_mode);
                
/* Game header checksum -- first 8 bytes must add up to 0x55 */
          
        if (mflag)
          multi_byte = multi;

        process_byte((char) (0x55 - startlo - starthi - multi_byte - control_byte - game_page_count - speed_low - speed_high),create_mode);
        process_byte(multi_byte,create_mode);
        process_byte(speed_low,create_mode);
        process_byte(speed_high,create_mode);
        if (kflag)
            printf("\nCreating 44khz .wav file");
        else
            printf("\nCreating 22khz .wav file");
        printf(" with a %0.2f second header tone\n",hmulti);
        if (dflag) {
            printf("File size = %d           ",fsize);
            printf("Start address is $%0.2x%0.2x\n",starthi,startlo);
            printf("Multi-Load byte   = $%2.2x    Page count        = $%2.2x\n",(unsigned char) multi_byte,(unsigned char) game_page_count);
            printf("Control-byte      = $%2.2x    Blank pages       = $%2.2x\n",(unsigned char) control_byte,(unsigned char) file_page_count - game_page_count);
        }

      }
          
/* Get appropriate page */

      if (!empty_page[page] || pflag)
        get_page(page,1);

/* If we just got the page, put the page header which consists of two bytes. The first byte */
/* is a counter that begins at zero for the first page and is incremented by 4 for each      */
/* subsequent page.  If the value is greater than 0x1f, then 0x1f is subtracted from it.     */
         
      if (scflag)
        pg_bank_byte = pg_bank_list[page];
      else
        pg_bank_byte = (page % 8) * 4 + init_bank + floor(page/8);

      sum = 0;
               
/* Get the sum of all 256 bytes of the current page */

      for(j=0;j<256;j++)
        sum += (char) buffer[j];

/* The second byte of the page header is 0x55 - the first byte - the sum of the 256 bytes of  */
/* program data.                                                                              */
                    
      in_byte = (char) (0x55 - pg_bank_byte - sum);

/* Put the program data */
    if (!empty_page[page] || pflag) {
      if (dflag)
        printf(" - bank %2.2d, page %2.2d, page&bank byte %2.2x, checksum %2.2x\n",(int)(pg_bank_byte&0x3),
                (int)((pg_bank_byte & 0xfc)/4),(unsigned char)pg_bank_byte,
                (unsigned char)in_byte);
      process_byte(pg_bank_byte,create_mode);
      process_byte(in_byte,create_mode);
      for(j=0;j<256;j++)
        process_byte(buffer[j],create_mode);
    }

/* Done with this page, increment page counter */

    page++;
  }

/* We are done, put a tone at the end of the game, unnecessary, but a nice touch */
/*
  for(k=1;k<1000;k++) {
    put_one_bit();
    put_zero_bit();
  }
  for(k=1;k<250;k++)
    process_byte(0x55,create_mode);
*/

  fclose(binfile);
  filecount++;

  strcpy(filename,strtok(NULL,"+"));

  if (!strcmp(filename,""))
    for(k=1;k<gaplength/8;k++)
      process_byte(0x55,create_mode);
  else
    for(k=1;k<gaplength/4;k++)
      process_byte(0x55,create_mode);

  } /* End of strtok While */

  if (create_mode == WAV_MODE) {

    /* Determine the file length for the WAV chunk of the .wav file */

      flength2 = flength + 36;

    /* Write the file length in hi-byte/lo-byte, hi-word/lo-word format to the .wav file */

      fseek(wavfile,0x28,SEEK_SET);
      length_lword[3] = 0;
      if (flength > 65535) {
        length_lword[2] = floor(flength / 65536);
        flength -= length_lword[2] * 65536;
      }
      if (flength > 256) {
        length_lword[1] = floor(flength / 256);
        flength -= length_lword[1] * 256;
      }
      length_lword[0] = flength;

      for(i=0;i<4;i++)
        fputc(length_lword[i],wavfile);

    /* The file length for the RIFF chunk of the .wav file is in flength2 */
    /* Write the file length in hi-byte/lo-byte, hi-word/lo-word format to the .wav file */

      fseek(wavfile,0x04,SEEK_SET);
      length_lword[3] = 0;
      if (flength2 > 65535) {
        length_lword[2] = floor(flength2 / 65536);
        flength2 -= length_lword[2] * 65536;
      }
      if (flength2 > 256) {
        length_lword[1] = floor(flength2 / 256);
        flength2 -= length_lword[1] * 256;
      }
      length_lword[0] = flength2;
      for(i=0;i<4;i++)
        fputc(length_lword[i],wavfile);
    }
    fclose(wavfile);
  } else {
    printf("\nMakewav 3.1 - May 23, 1997\n");
    printf(" Written By: Bob Colbert (rcolbert@novia.net)\n"); 
    printf(" Usage: makewav [flags] BINFILE[+BINFILE2...+BINFILEx] WAVFILE \n\n");
    printf("   flags:\n");
    printf("     -cxx   = Set control byte (2 digit hex)\n");
    printf("     -d     = Display debug information\n");
    printf("     -f     = Make a super-fastload .wav file\n");
    printf("     -h#    = Set header tone length in seconds\n");
    printf("     -k     = Use 44khz Waveforms\n");
    printf("     -mxx   = Set multiload byte (2 digit hex)\n");
    printf("     -p     = Turn \"blank page\" packing OFF\n");
    printf("     -r     = Create raw data file instead of a .wav file\n");
    printf("     -sxxxx = Set start address (4 digit hex)\n");
    printf("     -v#    = Set volume level of .wav file (1-10)\n\n");
  }
}
