// pcm2wav.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <windows.h>

#ifdef BIG_ENDIAN
    typedef short INT4;
#else
    typedef int INT4;
#endif


struct RIFF_CHUNK {
    char riff[4];
    INT4 length;
    char wave[4];
};

struct FORMAT_CHUNK {
    char fmt[4];
    INT4 fmtLen;
    char null[2];
    char numChannels[2];
    INT4 sampleRate;
    INT4 bytePerSecond;
    char bytePerSample[2];
    char bitPerSample[2];
};

struct DATA_CHUNK {
    char data[4];
    INT4 dataLen;
};
    
static const char *usage =
"\nUsage:\n"
"    pcm2wav [options] pcm_file wav_file\n"
"OPTIONS:\n"
"    -c <number of channels> : 1 for mono or 2 for stereo\n"
"    -r <sample rate in hz> : default 8000\n"
"    -b <bytes per sample>: 8 or 16\n"
"    -p : playback the generated wav file (windows only)\n"
"    -s : byte swapping required (for 16 bit case only)\n\n";

int main(int argc, char *argv[])
{
    struct FORMAT_CHUNK format;
    int numChannels = 1;
    int sampleRate = 8000;
    int bytePerSample = 2;
    int byteSwapping = 0;
    int playbackWav = 0;
    
    // parse command line options
    if (argc<3) {
        fprintf(stderr, "%s", usage);
        exit(1);
    }
    int opt;
    while ( (opt = getopt(argc-2, argv, "c:r:b:sp")) != -1 ) {
        switch(opt) {
            case 'c':
                numChannels = (int)strtol(optarg, 0, 10);
                if (numChannels != 1 && numChannels != 2) {
                    fprintf(stderr, "number of channels has to be 1 or 2\n");
                    fprintf(stderr, "using default value 1\n");
                    numChannels = 1;
                }
                break;
            case 'r':
                sampleRate = (int)strtol(optarg, 0, 10);
                break;
            case 'b':
                bytePerSample = (int)strtol(optarg, 0, 10);
                if (bytePerSample != 1 && bytePerSample != 2 &&
                        bytePerSample != 4) {
                    fprintf(stderr, "bit per sample has to be 1, 2 or 4\n");
                    fprintf(stderr, "using default value 2\n");
                    bytePerSample = 2;
                }
                break;
            case 's':
                byteSwapping = 1;
                break;
            case 'p':
                playbackWav = 1;
                break;
        }
    }

    // open the pcm file for read
    struct stat buf;
    if (stat(argv[argc-2], &buf) != 0) {
        fprintf(stderr, "Can not open %s for read\n", argv[argc-2]);
        exit(1);
    }
    long pcmFileSize;
    pcmFileSize = (long)(buf.st_size);
    FILE *pcmFile;
    pcmFile = fopen(argv[argc-2], "rb");
    if (pcmFile == NULL) {
        fprintf(stderr, "Can not open input file %s\n", argv[argc-2]);
        exit(2);
    }
    char *pcmData;
    pcmData = new char[pcmFileSize];
    fread(pcmData, sizeof(char), 4, pcmFile);
    pcmData[4] = '\0';
    if (strcmp(pcmData, "PCM8") == 0) {
        printf("input file is in IBM Private PCM8 format\n");
        pcmFileSize = pcmFileSize - 4;
        fread(pcmData, sizeof(char), pcmFileSize, pcmFile);
    } else {
        printf("input file is RAW PCM Data\n");
        fread(pcmData+4, sizeof(char), pcmFileSize-4, pcmFile);
    }

    // byte swapping if necessary
    if (byteSwapping==1 && bytePerSample!=1) {
        char tmp;
        for (int i=0;i<pcmFileSize;i+=2) {
            tmp = *(pcmData+i+1);
            *(pcmData+i+1) = *(pcmData+i);
            *(pcmData+i) = tmp;
        }
    }
    
    // open the wave file for write
    FILE *wavFile;
    wavFile = fopen(argv[argc-1], "wb");
    if (wavFile == NULL) {
        fprintf(stderr, "Can not open %s for write\n", argv[argc-1]);
        exit(2);
    }

    // construct the RIFF chunk
    struct RIFF_CHUNK riff;
    sprintf(riff.riff, "%c%c%c%c", 'R', 'I', 'F', 'F');
    riff.length = pcmFileSize + sizeof(RIFF_CHUNK) - 8
                + sizeof(struct FORMAT_CHUNK)
                + sizeof(struct DATA_CHUNK);
    sprintf(riff.wave, "%c%c%c%c", 'W', 'A', 'V', 'E');

    // construct the FORMAT chunk
    sprintf(format.fmt, "%c%c%c%c", 'f', 'm', 't', ' ');
    format.fmtLen = 0x10;
    sprintf(format.null, "%c%c", 0x01, 0x00);
    sprintf(format.numChannels, "%c%c", numChannels, 0x00);
    format.sampleRate = sampleRate;
    format.bytePerSecond = sampleRate * bytePerSample ;
    sprintf(format.bytePerSample, "%c%c", bytePerSample, 0x00);
    sprintf(format.bitPerSample, "%c%c", bytePerSample*8, 0x00);

    // construct the header of the DATA CHUNK
    struct DATA_CHUNK data;
    sprintf(data.data, "%c%c%c%c", 'd', 'a', 't', 'a');
    data.dataLen = pcmFileSize;
    
    // write the chunks
    fwrite(&riff, sizeof(riff), 1, wavFile);
    fwrite(&format, sizeof(format), 1, wavFile);
    fwrite(&data, sizeof(data), 1, wavFile);
    fwrite(pcmData, sizeof(char), pcmFileSize, wavFile);

    // close the file
    fclose(wavFile);
    fclose(pcmFile);
    if (pcmData)
        delete [] pcmData;
    
    if (playbackWav) {
        printf("playing back %s\n", argv[argc-1]);
        PlaySound(argv[argc-1], NULL, SND_FILENAME);
    }
    return(0);
}