/* Noise shapes 24bit input into 16bit output */

#include <stdio.h>
#include "kernel.h"
#include <swis.h>
#include <math.h>
#include <stdlib.h>

_kernel_swi_regs rin,rout;

FILE* infile;
FILE* outfile;

char infilename[1024];
char outfilename[1024];

char inleafname[1024];
char outleafname[1024];

char command[1024];

char ChunkID[5],Format[5],Subchunk1ID[5],Subchunk2ID[5];
int ChunkSize,Subchunk1Size,AudioFormat;
int NumChannels,SampleRate,ByteRate,BlockAlign;
int BitsPerSample,Subchunk2Size,BytesPerSample;

int blocksize, chunk_size, block_size, pairs_per_block, chunk_number;

int outsize16;

int sect_size, sect_number, number_of_sects;

int tick, clipped, clip_amount;

int i, ok, icut, icut2, icut24, icut224;

char datablock[230400];
char outblock[153600];

int left_in, right_in, left_out, right_out;

int erout_left, erin_left, sc_left, xnow_left;
int erout_right,erin_right,sc_right,xnow_right;

char string1[1024], string2[1024];

int check_inputfile(void);

void read_header(void);
void write_output_header(void);

int check_header(void);

int clip_check(int);

void get4(char*);
int get4int(void);
int get2int(void);


void write4int(int);
void write2int(int);
void write4(char*);

int quantise16(int);
int dither8lsbits(void);


int load_block(void);
void convert_block(void);

void read_settings_file(void);

void save_block(void);

int main(void)
{
  
  printf("WAV Noise Shaping Version 0.20 Feb 17 2017\n");
  printf("==========================================\n");
  
  read_settings_file();
  
  icut=32768; icut2=icut*2;
  icut24=icut*256; icut224=icut2*256;
  erout_left=0; erout_right=0;
  erin_left=0; erin_right=0;
  tick=0;
  
  printf(" input file name => ");
  
  scanf("%s",inleafname); 
  
  sprintf(infilename,"%s.%s",string1,inleafname);
  sprintf(outfilename,"%s.ns_%s",string2,inleafname);
  
  printf("Input from: %s\n\n",infilename);
  
  printf("Output to: %s\n\n",outfilename);
  
  ok=check_inputfile();
  
  if(ok>0)
  {
    
    
    
  
    read_header();
    
    ok=check_header();
    
    
    if (ok==0)
    {
      printf(" * Input not 24bit ! *\n  Halted\n");
    }
    else
    {
      printf("Processing\n Input -> output\n");
      blocksize=ByteRate/10;
      outsize16=(2*blocksize)/3;
      printf("Block sizes: %d -> %d\n",blocksize,outsize16);
      pairs_per_block=blocksize/(2*BytesPerSample);
      printf("Sample pairs per block = %d\n",pairs_per_block);
      
      outfile=fopen(outfilename,"w");
    
      write_output_header();
      
      infile=fopen(infilename,"r");
      fread(datablock,sizeof(char),44,infile);
      chunk_number=0;
      
      do
      {
        chunk_size=load_block();
        
        outsize16 = (2*chunk_size)/3;
        
        convert_block();
        
        save_block();
        
        chunk_number++;
        
        if(clipped==1)
        {
          printf(" * Clipped! %6.1lf by %d *\n",0.1*(double)chunk_number,clip_amount);
        }
        
        tick++;
        
        if(tick==50)
        {
          printf(" %6.1lf\n", 0.1*(double)chunk_number);
          tick=0;
        }
        
      } while (chunk_size==blocksize);
      
      
      fclose(outfile);
      fclose(infile);
      
      sprintf(command,"settype %s &FB1\0",outfilename);
      _kernel_oscli(command);
    }
  
  }                                                             
  
}


void read_settings_file(void)
{
  /* Reads the settings file inside the application to allow
     some flexibility of the program settings */
  
  FILE* setfile;
  setfile=fopen("<Obey$Dir>.Settings\0","rt");
  
  fscanf(setfile,"%s",string1);
  printf("Source file dir = %s\n\n",string1);
  fscanf(setfile,"%s",string2);
  printf("Output file dir = %s\n\n",string2);
  
}

int check_header(void)
{
  int answer;
  answer=1;
  if (BitsPerSample!=24)
  {
    answer=0;
    printf("Input file not 24 bit!\n");
  }
  if (SampleRate>384000)
  {
    answer=0;
    printf("Input file rate above 384k!\n");
  }
  return answer;
}



void save_block(void)
{
  fwrite(outblock,sizeof(char),outsize16,outfile);
}

void convert_block(void)
{
  /* This routine does the legwork and applies a Noise Shaped conversion */
  
  int io,ii_in,ii_out,lt,rt;
  char l1,l2,r1,r2;
  ii_in=0; ii_out=0; io=0; clipped=0; clip_amount=0;
  
  do
  {
    lt= 65536*(int)datablock[ii_in+2] + 256*(int)datablock[ii_in+1] + (int)datablock[ii_in];
    ii_in+=3;
    
   
    rt= 65536*(int)datablock[ii_in+2] +256*(int)datablock[ii_in+1] + (int)datablock[ii_in];
    ii_in+=3;
     
    if (lt>=icut24) lt-=icut224;
    if (rt>=icut24) rt-=icut224;
    left_in=lt;    right_in=rt;
    
    erout_left=erin_left; erout_right=erin_right;       /* update delays */
    
    sc_left=left_in-erout_left;
    left_out=quantise16( sc_left + dither8lsbits() );   /* shape left channel */
    erin_left= (256 * left_out) - sc_left;
    
    sc_right=right_in-erout_right;
    right_out=quantise16( sc_right + dither8lsbits() ); /* shape right channel */
    erin_right= (256 * right_out) - sc_right; 
    
    lt=left_out; rt=right_out;
    
    /* Now check for clipping */
    
    /* old way...
    
    if(lt > 32767) 
    {
      lt=  32767;
      clipped=1;
    }
    if(lt < -32767)
    {
       lt= -32767;
       clipped=1;
    }    
    if(rt >  32767)
    {
      rt=  32767;
      clipped=1;
    }     
    if(rt < -32767)
    {
      rt= -32767;
      clipped=1;
    }
    
    */
    
    lt=clip_check(lt);
    rt=clip_check(rt);
        
    if(lt<0) lt+=2*icut;
    if(rt<0) rt+=2*icut;
    
    l1=(char)((lt>>8)&255);
    l2=(char)(lt&255);
    r1=(char)((rt>>8)&255);
    r2=(char)(rt&255);
    
    outblock[ii_out]=l2;
    outblock[ii_out+1]=l1;
    outblock[ii_out+2]=r2;
    outblock[ii_out+3]=r1;
    
    ii_out+=4;
    
    io++;
  } while (io<pairs_per_block);
}


int clip_check(int insample)
{
  int outsample, icd;
  
  icd=0; outsample=insample;
  
  if( insample > 32767)
  {
    outsample=32767;
    clipped=1;
    icd=insample-outsample;
  }
  
  if(icd > clip_amount) clip_amount=icd;
  
  if( insample < -32767)
  {
    outsample= -32767;
    clipped=1;
    icd=outsample-insample;
  }
  
  if(icd > clip_amount) clip_amount=icd;
    
  return outsample;
  
}


int load_block(void)
{
  /* This procedure loads the next 0.1 second block of
     data from the input file. */
  
  int block_size_read;
  block_size_read=fread(datablock,sizeof(char),blocksize,infile);
  return block_size_read;
}


int quantise16(int iq)
{
  int answer;
  
  if (iq>=0)
  {
    iq+=128;
  }
  else
  {
    iq-=128;
  }
  
  answer=iq/256;
  
  return answer;
  
}

int dither8lsbits(void)
{
  /* This procedure returns a 'random dither'
  levet as an integer in the range +256 to -256 */
  
  int answer;
  
  answer=rand()%256 + rand()%256 - 256;
  return answer;
}

void write_output_header(void)
{
  write4(ChunkID);
  write4int(ChunkSize);
  write4(Format);
  write4(Subchunk1ID);
  write4int(Subchunk1Size);
  write2int(AudioFormat);
  write2int(NumChannels);
  write4int(SampleRate);
  write4int( ((2*ByteRate)/3) );
  printf("Output Byte rate = %d\n",(2*ByteRate)/3);
  
  write2int( (2*BlockAlign/3) );
  printf("Output Block Align = %d\n", (2*BlockAlign)/3);
  
  write2int( (2*BitsPerSample)/3) ;
  printf("Output bits per sample = %d\n", (2*BitsPerSample)/3);
  
  write4(Subchunk2ID); 
  write4int( (2*Subchunk2Size)/3);
  printf("Output payload = %d\n",(2*Subchunk2Size)/3);
  
  
}



void read_header(void)
{
  
    printf("Reading header\n");
    infile=fopen(infilename,"rd");
    get4(ChunkID);
    printf("ChunkID = %s\n",ChunkID);
    ChunkSize=get4int();
    printf("Chunk Size = %d\n",ChunkSize);
    get4(Format);
    printf("Format = %s\n",Format);
    get4(Subchunk1ID);
    printf("Subchunk1ID = %s\n",Subchunk1ID);
    Subchunk1Size=get4int();
    AudioFormat=get2int();
    
    NumChannels=get2int();
    printf("Number of Channels = %d\n",NumChannels);
    SampleRate=get4int();
    printf("Sample Rate = %d\n",SampleRate);
    ByteRate=get4int();
    printf("Byte Rate = %d\n",ByteRate);
    BlockAlign=get2int();
    printf("Block Align = %d\n",BlockAlign);
    BitsPerSample=get2int();
    printf("Bits per sample = %d\n",BitsPerSample);
    BytesPerSample=BitsPerSample/8;
    printf("Bytes per sample = %d\n",BytesPerSample);
    get4(Subchunk2ID);
    printf("Subchunk2ID = %s\n",Subchunk2ID);
    Subchunk2Size=get4int();
    printf("Subchunk2Size = %d [Number of Data payload bytes]\n",Subchunk2Size);
    printf("(Header size = %d)\n",ok-Subchunk2Size);
    fclose(infile);
    blocksize=ByteRate;
    printf("Block Size for 1 sec %d Bytes\n\n",blocksize);
    blocksize=blocksize/10;
    printf("Block Size for 0.1 sec %d Bytes\n\n",blocksize);
}


void write4int(int inv)
{
  char cop1,cop2,cop3,cop4;
  
  cop1=(char)(inv&255);
  cop2=(char)((inv>>8)&255);
  cop3=(char)((inv>>16)&255);
  cop4=(char)((inv>>24)&255);
  
  fprintf(outfile,"%c",cop1);
  fprintf(outfile,"%c",cop2);
  fprintf(outfile,"%c",cop3);
  fprintf(outfile,"%c",cop4);
}


void write2int(int inv)
{
  char cop1,cop2;
  
  cop1=(char)(inv&255);
  cop2=(char)((inv>>8)&255);
  
  fprintf(outfile,"%c",cop1);
  fprintf(outfile,"%c",cop2);
  
}

void write4(char* outstring)
{
  int isp;
  
  isp=0;
  do
  {
    fprintf(outfile,"%c",outstring[isp]);
    isp++;
  } while (isp<4);
  
}


int get2int(void)
{
  int result;
  char cip1,cip2;
  
  fscanf(infile,"%c",&cip1);
  fscanf(infile,"%c",&cip2);

  result=(int)cip2;
  result=(int)cip1+256*result;

  return result;
} 

int get4int(void)
{
  int result;
  char cip1,cip2,cip3,cip4;
  
  fscanf(infile,"%c",&cip1);
  fscanf(infile,"%c",&cip2);
  fscanf(infile,"%c",&cip3);
  fscanf(infile,"%c",&cip4);
    
  result=(int)cip4;
  result=(int)cip3+256*result;
  result=(int)cip2+256*result;
  result=(int)cip1+256*result;
  
  return result;
} 

void get4(char* outstring)
{
  int isp;
  char csp;
  isp=0;
  do
  {
    fscanf(infile,"%c",&csp);
    outstring[isp]=csp;
    isp++;
  } while (isp<4);
  outstring[4]=(char)0;

}


int check_inputfile(void)
{
  
  /* This procedure checks that the named input file exists
     and if it does, reports its duration */
  
  int ilb;
  
  
  rin.r[0]=5;
  rin.r[1]=(int)infilename;
  _kernel_swi(OS_File,&rin,&rout);
  if(rout.r[0]==1)
  {
    ilb=rout.r[4];
    
    
    
    printf("Input file %d bytes long\n\n",ilb);
  }
  else
  {
    ilb=0;
    
    printf("Input file does not exist!\n");
  }
  return ilb;
}

