// wavLibrary.cc  P. Conrad   CISC181 Fall 2005
// Routines for writing WAV files from sine waves

// @@@ ADD COMMENT HERE.. 
// Modified by @@@ insert your name, date, lab section
// for project 1b for CISC181, Fall 2005

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;


// the following header files are needed for writing binary files
// they provide the functions open() and write(), as well as O_CREAT, O_RDWR

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdio.h> // for perror

#include <cmath>   // for sin, floor
#include <cstdlib> // for atoi, atof


#include "endian.h"

#include "wavLibrary.h"

void writeWAVheader(int numSamples, int samplesPerSecond, int fd)
{
  
  char riffHeader[] = "RIFF";
  write(fd, riffHeader, 4);
  
  int lengthField = convertToLittleEndian((numSamples * 2) + 36);
  write(fd, &lengthField, 4);
  
  char WAVEfmt_Headers[] = "WAVEfmt ";
  write(fd, WAVEfmt_Headers, 8);
  
  int formatChunkSize = convertToLittleEndian(16);
  write(fd, &formatChunkSize, 4);
  
  signed short numChannels = 1;
  signed short numChannelsLittleEndian = 
    convertShortToLittleEndian(numChannels);
  write(fd, &numChannelsLittleEndian, 2);
  
  signed short compression = 1;
  signed short compressionLittleEndian = 
    convertShortToLittleEndian(compression);
  write(fd, &compressionLittleEndian, 2);
  
  int samplesPerSecondField = convertToLittleEndian(samplesPerSecond);
  write(fd, &samplesPerSecondField, 4);
  
  int bytesPerSecondField = convertToLittleEndian(samplesPerSecond * 2);
  write(fd, &bytesPerSecondField, 4);
  
  signed short bytesPerSample = 2;
  signed short bytesPerSampleLittleEndian = 
    convertShortToLittleEndian(bytesPerSample);
  write(fd, &bytesPerSampleLittleEndian, 2);
  
  signed short bitsPerSample = 16;
  signed short bitsPerSampleLittleEndian = 
    convertShortToLittleEndian(bitsPerSample);
  write(fd, &bitsPerSampleLittleEndian, 2);
  
  
  char dataHeader[] = "data";
  write(fd, dataHeader, 4);
  
  int dataLength = convertToLittleEndian(numSamples * 2);
  write(fd, &dataLength, 4);
}




  


void  writeSilenceSamples(int fd, double duration)
{
  double t = 0;
  signed short sixteenBitSample;
  int numSamples = (int) floor (duration * 11025);
  for (int i=0; i<numSamples; i++)
    {
      sixteenBitSample = 0;
      write(fd,&sixteenBitSample,2);
      t += 1.0/11025.0;
    }
}

void  writeSineWaveSamples(int fd, double duration, double freq1 )
{
  
  double t = 0;
  double sample, sample1, sample2;
  signed short sixteenBitSample;
  int numSamples = (int) floor (duration * 11025);
  for (int i=0; i<numSamples; i++)
    {
      sample = sin(freq1 * 2 * M_PI * t);
      sixteenBitSample = (signed short) floor(sample * 32767);
      sixteenBitSample = convertShortToLittleEndian(sixteenBitSample);
      write(fd,&sixteenBitSample,2);
      t += 1.0/11025.0;
    }
}

void  writeNoiseSamples(int fd, double duration, double volume)
{
  
  double t = 0;
  signed short sixteenBitSample;
  int numSamples = (int) floor (duration * 11025);

  double sample;

  for (int i=0; i<numSamples; i++)
    {
      sample = volume *  ( ( rand() % 65536 ) - 32768 ); // generate random number between 32768 and 32767
      sixteenBitSample = (signed short) floor(sample);
      sixteenBitSample = convertShortToLittleEndian(sixteenBitSample);
      write(fd,&sixteenBitSample,2);
      t += 1.0/11025.0;
    }
}



double lengthOfC(double ditDuration,
		 double dahDuration,
		 double spaceInsideChars)
{
  // C in Morse code is -.-.
  return(2.0 * dahDuration + 
	 2.0 * ditDuration + 
	 3.0 * spaceInsideChars);
}

// We still pass in dahDuration even though it is not needed;
// having a consistent function prototype for all "lengthOf__" functions
// will make coding easier.

double lengthOfI(double ditDuration,
		 double dahDuration,
		 double spaceInsideChars)
{
  // I in Morse code is ..
  return(2.0 * ditDuration + 
	 1.0 * spaceInsideChars);
}

double lengthOfS(double ditDuration,
		 double dahDuration,
		 double spaceInsideChars)
{
  // S in Morse code is ...
  return(3.0 * ditDuration + 
	 2.0 * spaceInsideChars);
}


void writeC(int fd, double ditDuration, double dahDuration,
	    double spaceInsideChars, double morseCodeFrequency)
{      
  // C is -.-.
  writeSineWaveSamples(fd, dahDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);
  
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);
  
  writeSineWaveSamples(fd, dahDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);
  
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
}

void writeI(int fd, double ditDuration, double dahDuration,
	    double spaceInsideChars, double morseCodeFrequency)
{      
  // I is ..
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);
  
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
}


void writeS(int fd, double ditDuration, double dahDuration,
	    double spaceInsideChars, double morseCodeFrequency)
{      
  // S is ...
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);

  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
  writeSilenceSamples(fd, spaceInsideChars);
  
  writeSineWaveSamples(fd, ditDuration, morseCodeFrequency);
}


void  writeDTMFSamples(int fd, double duration, int freq1, int freq2)
{
  
  double t = 0;
  double sample, sample1, sample2;
  signed short sixteenBitSample;
  int numSamples = (int) floor (duration * 11025);
  for (int i=0; i<numSamples; i++)
    {
      sample1 = sin(freq1 * 2 * M_PI * t);
      sample2 = sin(freq2 * 2 * M_PI * t);
      sample = (sample1 + sample2) / 2.0;
      sixteenBitSample = (signed short) floor(sample * 32767);
      sixteenBitSample = convertShortToLittleEndian(sixteenBitSample);
      write(fd,&sixteenBitSample,2);
      t += 1.0/11025.0;
    }
}

const int DTMF1a = 697; const int DTMF1b = 1209;
const int DTMF2a = 697; const int DTMF2b = 1336;
const int DTMF3a = 697; const int DTMF3b = 1477;

const int DTMF4a = 770; const int DTMF4b = 1209;

// @@@ TODO: finish the table of constants
// @@@ NOTE: you must add extern definition to the .h file also!

const int DTMF_star_a = 941;  const int DTMF_star_b = 1477;
const int DTMF0a = 941;       const int DTMF0b = 1477;
const int DTMF_pound_a = 941; const int DTMF_pound_b = 1477;

const int dialTone_a = 350; const int dialTone_b = 440;

//  @@@ TODO: add constants for the busy signal frequencies
//  @@@ and the on/off durations.   


void writeDTMFTones(int fd, double toneDuration, char c)
{
  switch(c)
    {
    case 1: 
    case '1':
      writeDTMFSamples(fd, toneDuration, DTMF1a, DTMF1b);
      break;

    case 2: 
    case '2':
      writeDTMFSamples(fd, toneDuration, DTMF2a, DTMF2b);
      break;

    case 3: 
    case '3':
      writeDTMFSamples(fd, toneDuration, DTMF3a, DTMF3b);
      break;

    case '*':
      writeDTMFSamples(fd, toneDuration, DTMF_star_a, DTMF_star_b);
      break;
      
    default:
      cerr << "Unknown character in writeDTMFTones " << c << endl;
      exit(1);
    }


}





// Explanation of Morse Code "ditDuration" etc.
// 
// It is said that Morse code is a binary code because there
// are two symbols: dot and dash.  This is not exactly correct.
// 
// In a more precise view, there are five symbols that a sender
// or receiver of Morse Code must 
// 
//    dot (called a "dit" by Morse Code users)
//    dash (called a "dah" by Morse Code users)
//    spaceInsideCharacter (e.g. in S, which is ..., the
//      period of silence between dit one and dit two, and
//      between dit two and dit three
//    spaceBetweenCharacters (e.g. in ... - - - ..., SOS, the period
//      of silence between the last dit in the S, and the first
//      dah in the O, and between the last dah in the O, and the
//      first dit in the S.
//    spaceBetweenWords (e.g. if you spelling out words, the
//      period of silence between the last char of one word,
//      and the first char of the next word.
// 
//     
// At a first approximation, according to
// http://www.netwalk.com/~fsv/CWguide.htm
// the relationships between these are as follows: 
// 
//    dahDuration = ditDuration * 3.0;
//    spaceInsideCharacter = ditDuration;
//    spaceBetweenCharacters = ditDuration * 3.0;
//    spaceBetweenWords = ditDuration * 7.0;
// 
// To convert from WordsPerMinute to the ditDuration,
// the word PARIS is used as a standard.  The word PARIS
// (followed by a spaceBetweenWords) adds up to 50 ditDurations.
// So the expression:
//
//   w  (words/minute) * (1 minute/60 seconds) * (50 ditDurations)/(1 word)
//
// yields a value in ditDurations / second.  The reciprocal of that value
// gives us seconds per ditDuration.  For example, 5 wpm gives us:
//
//  5 (words/minutes) * (1 minutes/60 seconds) * (50 ditDurations)/(1 word)
// 
//  = (5 * 50)/60 = 250/60 = 4.166666666667 ditDurations / second
//  = 0.24 seconds per ditDuration
//
//  The PARIS standard is described at:
//    http://www.ac6v.com/morseaids.htm#MS
//
// However, in an effort to improve the understandability of code,
// "Farnsworth timing" is often used instead.  In Farnsworth
// timing, you use a fast wpm value for the dit,dah, and space between
// characters, and then add extra space to the spaceBetweenChars and 
// spaceBetweenWords, to acheive a slower overall wpm value.
//
// The following document specifies formulas for Farnsworth timing.
//  http://www.arrl.org/files/infoserv/tech/code-std.txt
//
// The way I read the document, the final formulas for 
// Tc and Tw given are "extra" time to be added to the 
// spaceBetweenCharacters and spaceBetweenWords.
//
// So, for example, when sending in Farnsworth timing at 5wpm/18wpm
// you would use the following (read the web page above for details)
// 
//     c = 18
//     s = 5
//
//     Ta = (60c - 37.2s)/sc
//     Tc = (3Ta)/19
//     Tw = (7Ta)/19
//
//  ditDuration, dahDuration and spaceInsideChars are taken from 18wpm, so
//    
//  18 (words/minutes) * (1 minutes/60 seconds) * (50 ditDurations)/(1 word)
// 
//  = (18 * 50)/60 = 900/60 = 15 ditDurations / second
//  = 0.0666666667 seconds per ditDuration
//  
//  dahDuration = 3 * ditDuration = 0.2 seconds
//  spaceInsideChars = ditDuration = 0.0666666667 seconds
//
// Ta = 9.933333333
// Tc = 1.568421053
// Tw = 3.659649123
//
// spaceBetweenChars =  ditDuration * 3  + Tc = 1.768421053
// spaceBetweenWords = ditDuration * 7  + Tw = 4.126315789











