#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>

#define PI 3.14159265359

enum PIANO
{
    KEY_C = 0,
    KEY_C_MAJOR = 1,
    KEY_D_MINOR = 1,
    KEY_D = 2,
    KEY_D_MAJOR = 3,
    KEY_E_MINOR = 3,
    KEY_E = 4,
    KEY_F = 5,
    KEY_F_MAJOR = 6,
    KEY_G_MINOR = 6,
    KEY_G = 7,
    KEY_G_MAJOR = 8,
    KEY_A_MINOR = 8,
    KEY_A = 9,
    KEY_A_MAJOR = 10,
    KEY_B_MINOR = 10,
    KEY_B = 11
};
const float KEY_FREQUENCIES[] = {261.626, 277.183, 293.665, 311.127, 329.628, 347.228, 369.994, 391.995, 415.305, 440.000, 466.164, 493.883};

typedef unsigned long uLong;
typedef unsigned char uChar;
typedef struct SAMPLE SAMPLE;
typedef struct STRING STRING;
struct SAMPLE
{
    long left;
    long right;
};
struct STRING
{
    uChar* data;
    uLong length;
};

void LittleEndian_long(uLong longToWrite, uChar* buffer, int length);
void PrintAsInt(STRING str);
void StringToFile(STRING string, FILE* file);
STRING WAV_WriteHeader(uLong fileSize);
STRING WAV_WriteFormat(void);
STRING WAV_WriteData(SAMPLE* samples, uLong length);
void SAMPLE_Clamp(SAMPLE* sample, uLong length, float initPercent);
void SAMPLE_MakeSilent(SAMPLE* sample, uLong length);
void SAMPLE_AddSineWave(SAMPLE* sample, uLong length, float pitch);
float FrequencyOf(int key, int octave);

int main(void)
{
    SAMPLE sampleARRAY[102400];
    SAMPLE* sampleData = sampleARRAY;
    STRING header;
    STRING format;
    STRING data;
    const int step = 10240;
	FILE* newFile = fopen("newWave.wav", "wb");
	if(newFile == NULL)
	{
		printf("Well... Dammit.");
		return 1;
	}
    srand(time(NULL));
    
    SAMPLE_MakeSilent(sampleData, 102400);
    SAMPLE_AddSineWave(sampleData,  step * 2, FrequencyOf(KEY_C, 4));
    SAMPLE_AddSineWave((sampleData += step * 2), step, FrequencyOf(KEY_D_MAJOR, 4));
    SAMPLE_AddSineWave((sampleData += step), step, FrequencyOf(KEY_D, 4));
    SAMPLE_AddSineWave((sampleData += step), step, FrequencyOf(KEY_D_MAJOR, 4));
    SAMPLE_AddSineWave((sampleData += step), step, FrequencyOf(KEY_D, 4));
    SAMPLE_AddSineWave((sampleData += step), step * 4, FrequencyOf(KEY_C, 4));
    sampleData = sampleARRAY;
    SAMPLE_Clamp(sampleData, 102400, .5f);
    format = WAV_WriteFormat();
    data = WAV_WriteData(sampleData, 102400);
    header = WAV_WriteHeader(format.length + data.length + 4);
    StringToFile(header, newFile);
    StringToFile(format, newFile);
    StringToFile(data, newFile);
    
	fclose(newFile);
	return 0;
}

float FrequencyOf(int key, int octave)
{
    int octMult = 1;
    octave -= 4;
    if(octave > 0)
    {
        octMult <<= octave;
        return KEY_FREQUENCIES[key] * (float)octMult;
    }
    else if(octave < 0)
    {
        octMult <<= octave;
        return KEY_FREQUENCIES[key] / (float)octMult;
    }
    return KEY_FREQUENCIES[key];
}

void LittleEndian_long(uLong longToWrite, uChar* buffer, int length)
{
	uLong currNum;
    int i;
	for(i = 0; i < length; ++i)
	{
		currNum = longToWrite;
		longToWrite >>= 8;
		longToWrite <<= 8;
		currNum ^= longToWrite;
        longToWrite >>= 8;
        buffer[i] = currNum;
	}
}

void PrintAsInt(STRING str)
{
    uLong i = 0;
    uChar* string = str.data;
    uLong length = str.length;
    while(i != length)
    {
        printf("%3X ", string[i]);
        ++i;
        if(i % 8 == 0)
        {
            printf("\n");
        }
    }
    if(i % 8 != 0)
    {
        printf("\n");
    }
}

void StringToFile(STRING string, FILE* file)
{
    while(string.length > 0)
    {
        fputc((int)*(string.data), file);
        ++string.data;
        --string.length;
    }
}

void SAMPLE_Clamp(SAMPLE* sample, uLong length, float initPercent)
{
    uLong i = 0;
    long maxValue = 0;
    long minValue = 0;
    long largestDelta = 32767;
    time_t lastTime = time(NULL) + 1;
    SAMPLE* origSample = sample;
    while(i < length)
    {
        sample->left = (long)((float)sample->left * initPercent);
        sample->right = (long)((float)sample->right * initPercent);
        if(sample->left > maxValue)
        {
            maxValue = sample->left;
        }
        else if(sample->left < minValue)
        {
            minValue = sample->left;
        }
        if(sample->right > maxValue)
        {
            maxValue = sample->right;
        }
        else if(sample->right < minValue)
        {
            minValue = sample->right;
        }
        ++sample;
        ++i;
        if(lastTime < time(NULL))
        {
            printf("SAMPLE_Clamp: %d / %d samples peeked...\n", i, length);
            lastTime = time(NULL);
        }
    }
    sample = origSample;
    minValue *= -1;
    if(maxValue > largestDelta)
    {
        largestDelta = maxValue;
    }
    if(minValue > largestDelta)
    {
        largestDelta = minValue;
    }
    printf("Maximum Sample Rate: %d\nMininum Sample Rate: %d\n", maxValue, minValue * -1);
    i = 0;
    while(i < length)
    {
        sample->left = (long)((float)sample->left / (float)largestDelta * 32767.f);
        sample->right = (long)((float)sample->right / (float)largestDelta * 32767.f);
        if(sample->left > 32767)
        {
            printf("\nSOMEONE IS AT FAULT (left over)\n");
            sample->left = 32767;
        }
        else if(sample->left < -32768)
        {
            printf("\nSOMEONE IS AT FAULT (left under)\n");
            sample->left = -32768;
        }
        if(sample->right > 32767)
        {
            printf("\nSOMEONE IS AT FAULT (right over)\n");
            sample->right = 32767;
        }
        else if(sample->right < -32768)
        {
            printf("\nSOMEONE IS AT FAULT (right under)\n");
            sample->right = -32768;
        }
        ++sample;
        ++i;
        if(lastTime < time(NULL))
        {
            printf("SAMPLE_Clamp: %d / %d samples clamped...\n", i, length);
            lastTime = time(NULL);
        }
    }
    printf("Finished SAMPLE_Clamp.\n");
}

void SAMPLE_MakeSilent(SAMPLE* sample, uLong length)
{
    uLong i = 0;
    time_t lastTime = time(NULL) + 1;
    while(i < length)
    {
        sample->left = 0;
        sample->right = 0;
        ++sample;
        ++i;
        if(lastTime < time(NULL))
        {
            printf("SAMPLE_MakeSilent: %d / %d samples written...\n", i, length);
            lastTime = time(NULL);
        }
    }
    printf("Finished SAMPLE_MakeSilent.\n");
}

void SAMPLE_AddSineWave(SAMPLE* sample, uLong length, float pitch)
{
    uLong i = 0;
    time_t lastTime = time(NULL) + 1;
    while(i < length)
    {
        int sineValue;
        float multValue = 0.f;
        if(i <= length / 4)
        {
            multValue = (float)i / (float)length * 4.f;
        }
        else if(i > length * 3 / 4)
        {
            multValue = (1.f - ((float)i / (float)length)) * 4.f;
        }
        else
        {
            multValue = 1.f;
        }
        sineValue = (long)((((sin((float)i / 44100.f * PI * 2 * pitch) + 1.f) / 2.f) * 65535 - 32768) * multValue);
        sample->left += sineValue;
        sample->right += sineValue;
        ++sample;
        ++i;
        if(lastTime < time(NULL))
        {
            printf("SAMPLE_AddSineWave: %d / %d samples written...\n", i, length);
            lastTime = time(NULL);
        }
    }
    printf("Finished SAMPLE_AddSineWave.\n");
}

STRING WAV_WriteHeader(uLong fileSize)
{
    int i;
    uChar size[4];
    uChar* headerStr = (uChar*)malloc(12);
    STRING returnString;
    returnString.data = headerStr;
    returnString.length = 12;
    headerStr[0] = 'R';
    headerStr[1] = 'I';
    headerStr[2] = 'F';
    headerStr[3] = 'F';
    LittleEndian_long(fileSize, size, 4);
    for(i = 0; i < 4; ++i)
    {
        headerStr[4+i] = size[i];
    }
    headerStr[8] = 'W';
    headerStr[9] = 'A';
    headerStr[10] = 'V';
    headerStr[11] = 'E';
    printf("Finished WAV_WriteHeader.\n");
    return returnString;
}

STRING WAV_WriteFormat(void)
{
    int i;
    uChar fourBytes[4];
    uChar twoBytes[2];
    uChar* formatStr = (uChar*)malloc(26);
    STRING returnString;
    returnString.data = formatStr;
    returnString.length = 26;
    formatStr[0] = 'f';
    formatStr[1] = 'm';
    formatStr[2] = 't';
    formatStr[3] = ' ';
    LittleEndian_long(18, fourBytes, 4);
    for(i = 0; i < 4; ++i)
    {
        formatStr[4+i] = fourBytes[i];
    }
    LittleEndian_long(1, twoBytes, 2);
    for(i = 0; i < 2; ++i)
    {
        formatStr[8+i] = twoBytes[i];
    }
    LittleEndian_long(2, twoBytes, 2);
    for(i = 0; i < 2; ++i)
    {
        formatStr[10+i] = twoBytes[i];
    }
    LittleEndian_long(44100, fourBytes, 4);
    for(i = 0; i < 4; ++i)
    {
        formatStr[12+i] = fourBytes[i];
    }
    LittleEndian_long(176400, fourBytes, 4);
    for(i = 0; i < 4; ++i)
    {
        formatStr[16+i] = fourBytes[i];
    }
    LittleEndian_long(4, twoBytes, 2);
    for(i = 0; i < 2; ++i)
    {
        formatStr[20+i] = twoBytes[i];
    }
    LittleEndian_long(16, twoBytes, 2);
    for(i = 0; i < 2; ++i)
    {
        formatStr[22+i] = twoBytes[i];
    }
    LittleEndian_long(0, twoBytes, 2);
    for(i = 0; i < 2; ++i)
    {
        formatStr[24+i] = twoBytes[i];
    }
    printf("Finished WAV_WriteFormat.\n");
    return returnString;
}

STRING WAV_WriteData(SAMPLE* samples, uLong length)
{
    unsigned long i;
    time_t lastTime = time(NULL) + 1;
    uChar twoBytes[2];
    uChar fourBytes[4];
    uChar* dataStr = (uChar*)malloc((length * 4) + 8);
    STRING returnString;
    uChar* dataStrPtr = dataStr + 8;
    returnString.data = dataStr;
    returnString.length = (length * 4) + 8;
    dataStr[0] = 'd';
    dataStr[1] = 'a';
    dataStr[2] = 't';
    dataStr[3] = 'a';
    LittleEndian_long(length * 4, fourBytes, 4);
    for(i = 0; i < 4; ++i)
    {
        dataStr[4 + i] = fourBytes[i];
    }
    for(i = 0; i < length; ++i)
    {
        LittleEndian_long(samples->left, twoBytes, 2);
        *dataStrPtr = twoBytes[0];
        ++dataStrPtr;
        *dataStrPtr = twoBytes[1];
        ++dataStrPtr;
        LittleEndian_long(samples->right, twoBytes, 2);
        *dataStrPtr = twoBytes[0];
        ++dataStrPtr;
        *dataStrPtr = twoBytes[1];
        ++dataStrPtr;
        ++samples;
        if(lastTime < time(NULL))
        {
            printf("WAV_WriteData: %d / %d samples written...\n", i + 1, length);
            lastTime = time(NULL);
        }
    }
    printf("Finished WAV_WriteData.\n");
    return returnString;
}