///////////////////////////////////////////////////////////////////////////////
// Doug Macdonald
// http://www.dougwillsave.us
// 
// Granular paint: Reads in a bitmap file and uses the color data within to
// create a sound file using granular synthesis. Each pixel in the image will
// create a grain used in the synthesis: the horizontal position indicates
// when the grain will begin taking effect, the vertical position indicates
// what the grain's frequency will be, and the luminance and chrominance 
// determine the grain's probability, duration, and volume.
//
// This program takes two parameters: the filename of a bitmap image, and a 
// number indicating the length of the generated sound file in seconds.
//
// Initially written as a course assignment at DigiPen Institute of Technology.
// © 2009 DigiPen (USA) Corporation
///////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <fstream>
#include <math.h>
#include <list>

using namespace std;

#define PI2 6.28318531f
#define MAX 32767.f

float frand(void) {
  return float(rand())/float(RAND_MAX);
}

struct Grain 
{
  float angular_frequency,
        amplitude,
        time_begin,
        duration,
        volume;
};

float Han(Grain g, float t) 
{
  //return 0.5f * (1 - cos( (PI2 * (t - g.time_begin)) / g.duration)) * sin(PI2 * g.angular_frequency * (t - g.time_begin));
  return ((t - g.time_begin) / g.duration) * sin(PI2 * g.angular_frequency * (t - g.time_begin));
}

float Y(float r, float g, float b)
{
  return 0.299f * r + 0.587f * g + 0.144f * b;
}

float Cb(float r, float g, float b)
{
  return -0.169f * r + 0.331f * g + 0.499f * b + 128;
}

float Cr(float r, float g, float b)
{
  return 0.499f * r + 0.418f * g + 0.0813f * b + 128;
}

int main(int argc, char *argv[]) 
{
  if (argc != 3)
    return 0;

  // Read in the bitmap
  fstream in(argv[1],ios_base::in|ios_base::binary);
  char bmp_header[54];
  in.read(bmp_header,54);
  int bmp_width = *reinterpret_cast<int*>(bmp_header+18),
    bmp_height = *reinterpret_cast<int*>(bmp_header+22),
    bmp_data_size = *reinterpret_cast<unsigned*>(bmp_header+34);

  char* bmp_data = new char[bmp_data_size];
  in.read (bmp_data, bmp_data_size);
  int bmp_stride = bmp_data_size / bmp_height;

  float length = static_cast<float>(atof(argv[2]));
  int sample_count = static_cast<int>(44100 * length);
  float* buffer = new float[sample_count];
  short* output = new short[sample_count];

  // Initial bookkeeping 
  list<Grain> grains;
  int index = 0;
  float dindex =  static_cast<float>(sample_count) / bmp_width;
  int index_last = -1;
  float dt = length / sample_count;
  float samples_per_column = static_cast<float>(sample_count) / bmp_width;
  float norm = min( 1.0f, 1.0f / (bmp_height * bmp_width * 0.001f));
  
  // first, zero out the buffer
  for (int i = 0; i < sample_count; ++i)
  {
    buffer[i] = 0.f;
  }

  for (int j = 0; j < bmp_width; ++j)
  {
      
    for (int i = 0; i < bmp_height; ++i)
    {

      // Read in the color values from the bitmap
      unsigned char red = bmp_data[bmp_stride*i+3*j+2],
        green = bmp_data[bmp_stride*i+3*j+1],
        blue = bmp_data[bmp_stride*i+3*j];

      if ( (frand() < 1 - Y(red, green, blue) / 255))
      {
        // Create a grain from this pixel based on its RGB values
        Grain g;
        g.time_begin = j * length / bmp_width;
        g.angular_frequency = 40 * pow(500, i / static_cast<float>(bmp_height));
        g.duration = 10 * max(static_cast<float>(length / bmp_width), 0.01f) * Cb(red, green, blue) / 255;
        g.volume = Cr(red, green, blue) / 255;

        int index_end = min(static_cast<int>((g.time_begin + g.duration) / dt), sample_count);
        for (int index = static_cast<int>(g.time_begin / dt); index < index_end; ++index)
        {
          // Add the contents of this pixel to each byte of the buffer affected by it
          buffer[index] += g.volume * Han(g, index * dt);
        }
      }
    }


  }

    // output the buffer
    for ( int i  = 0; i < sample_count; ++i)
    {
      output[i] =  short(max(-MAX,min(MAX,buffer[i] * norm * MAX)));
    }


  // Print the data to a WAV file.
  fstream out("granular_paint.wav",ios_base::binary|ios_base::out);
  char header[44] = {'R','I','F','F',0,0,0,0,'W','A','V','E',
                     'f','m','t',' ',0,0,0,0,0,0,0,0,0,0,0,0,
                     0,0,0,0,0,0,0,0,'d','a','t','a',0,0,0,0};
  unsigned data_size = 2*sample_count;
  *reinterpret_cast<unsigned*>(header+4) = 36u + data_size;
  *reinterpret_cast<unsigned*>(header+16) = 16;
  *reinterpret_cast<unsigned short*>(header+20) = 1;
  *reinterpret_cast<unsigned short*>(header+22) = 1;
  *reinterpret_cast<unsigned*>(header+24) = 44100;
  *reinterpret_cast<unsigned*>(header+28) = 2*44100;
  *reinterpret_cast<unsigned short*>(header+32) = 2;
  *reinterpret_cast<unsigned short*>(header+34) = 16;
  *reinterpret_cast<unsigned*>(header+40) = data_size;
  out.write(header,44);
  out.write(reinterpret_cast<char*>(output),data_size);
  out.close();

  delete[] bmp_data;
  delete[] buffer;
  delete[] output;
  return 0;
}

