/* 
    Spectrum Analyzer
    (C) 2007 Jason Hunt
    GPL

    file: sound.cpp
*/

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <math.h>

#include "sound.h"
#include "debug.h"

int sound_running = 1;                  // true if soundsystem is running
double sound_sampletime = 0.05;         // Period between captures
int samples_per_second = 22050;

// data for sound system
HWAVEIN sound_in_handle;
WAVEFORMATEX  sound_format;
WAVEHDR	sound_header[2];  // headers for sound double buffer 
HANDLE sound_thread_handle;

CRITICAL_SECTION data_lock;             // Lock for processed data

double raw_sound_data[max_sample];
double processed_data[max_sample];  

int n_samples = 0;                  // Number of samples in last chunk

// Storage for transform
double ft_const_1[max_sample][max_sample/2+1][2];
double ft_const_2[max_sample];
double ft_result[max_sample][2];


/* Copy data into local buffer (Thread safe) */
void copy_sample_data(double *d, int &s)
{
    EnterCriticalSection(&data_lock);

     s = n_samples;
 
    for (int i = 0; i < s; i++)
        d[i] =  processed_data[i];//raw_sound_data[i];
     
    LeaveCriticalSection(&data_lock);
}
/* copy_sample_data */


/* preform Fourier transformation */
void process_data( int samples )
{
    if (samples > max_sample) samples = max_sample; // Clamp to max samples

    // Check for change in sample size
    if (n_samples != samples)       
    {
        // Store new sample size
        n_samples = samples;        
    
        // Compute constants
        for (unsigned int n = 0; n <= samples / 2; n++)
            for (unsigned int e = 0; e < samples; e++)
            {
                double dn = 2 * M_PI * n* e / (double)samples;

                ft_const_1[e][n][0] = cos(dn);
                ft_const_1[e][n][1] = -sin(dn);
            }


        ft_const_2[0] = 1/(double)(samples);

        for (unsigned int n = 1; n < samples / 2; n++)
            ft_const_2[n] = 2/(double)samples;
        
        ft_const_2[samples / 2] = 1 / (double)(samples);           
    }


   
    // Preform calculation
    for (unsigned int n = 0; n <= samples / 2; n++)
    {
        ft_result[n][0] = 0;
        ft_result[n][1] = 0;
  
        for (unsigned int e = 0; e < samples; e++)
        {
            ft_result[n][0] += raw_sound_data[e] * ft_const_2[n] * ft_const_1[e][n][0];
            ft_result[n][1] += raw_sound_data[e] * ft_const_2[n] * ft_const_1[e][n][1];
        }    
    
    }


    EnterCriticalSection(&data_lock);   // Lock processed data
    
    // Compute amplitude and store result
    for (unsigned int n = 0; n <= samples / 2; n++)
    {
        if (n > 5)
            processed_data[n] = sqrt(ft_result[n][0] * ft_result[n][0] +
                                     ft_result[n][1] * ft_result[n][1]);
        else
            processed_data[n] = 0;                                 
    }

    LeaveCriticalSection(&data_lock);
}
/* End of process data */



/* thread the handle sound messages */
DWORD WINAPI sound_thread(LPVOID arg)
{
    int inside_trigger = 0;

	MSG	 message;


	while (GetMessage(&message, 0, 0, 0) == 1)
	{
        // windows sound message?
		if (message.message == MM_WIM_DATA)
		{
            // extract number of bytes
            unsigned int num_bytes = ((WAVEHDR *)message.lParam)->dwBytesRecorded;

            // fail safe
            if (num_bytes)
            {
                // calculate average of RMS data
                
                double sum = 0;
                
                for (unsigned int i = 0; i < num_bytes / 2 ; i++)
                {
                    // extract an unsiged short from the raw data
                    
                    snd_store t;
                        
                    t.bytes[0] = sound_header[1].lpData[2*i];
                    t.bytes[1] = sound_header[1].lpData[2*i+1];
                           
                    //sum += abs(t.value);                    
                    
                    if (i < max_sample)
                      raw_sound_data[i] = t.value;
                    
                }    

                process_data(num_bytes/2);
 
            }			

            // switch to the next buffer
            if (sound_running)
                waveInAddBuffer(sound_in_handle, (WAVEHDR *)message.lParam, sizeof(WAVEHDR));
        }     
        
        // return from thread
		if (message.message == MM_WIM_CLOSE) return(0);    
                                          
    }
}     
/* end of sound thread */




/* starts the soiund system */
void start_sound( void )
{
    //add_debug("start_sound", "Start sound");
    
    InitializeCriticalSection(&data_lock);
    
	MMRESULT error;
   
    // setup recording thread
    sound_thread_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)sound_thread, 0, 0, (DWORD*)&error);

	if (!sound_thread_handle )
	{
       add_debug("start_sound", "Unable to start thread");
       return;
	}

	CloseHandle(sound_thread_handle );

    // setup sound structure
    // 16bit, 44khz, both channels
    sound_format.wFormatTag = WAVE_FORMAT_PCM;
    sound_format.nChannels = 1;
//    sound_format.nSamplesPerSec = 11025;//22050;//44100;
    sound_format.nSamplesPerSec = samples_per_second ;//44100;

    sound_format.wBitsPerSample = 16;
    sound_format.nBlockAlign = sound_format.nChannels * (sound_format.wBitsPerSample/8);
    sound_format.nAvgBytesPerSec = sound_format.nSamplesPerSec * sound_format.nBlockAlign;
    sound_format.cbSize = 0;

    // open preferred wave device
    if (waveInOpen(&sound_in_handle, WAVE_MAPPER, &sound_format, (DWORD)error, 0, CALLBACK_THREAD))
    {
       add_debug("start_sound", "Unable to start sound");
       return;
    }    

    // clear header data
	ZeroMemory(&sound_header[0], sizeof(WAVEHDR) * 2);

    // setup buffer sizes
    sound_header[1].dwBufferLength = (DWORD)(sound_format.nAvgBytesPerSec * sound_sampletime);
    sound_header[0].dwBufferLength = (DWORD)(sound_format.nAvgBytesPerSec * sound_sampletime);

    // get memmory for both buffers
    if (!(sound_header[0].lpData = (char *)VirtualAlloc(0, sound_header[0].dwBufferLength * 2, MEM_COMMIT, PAGE_READWRITE)))
	{
        add_debug("start_sound", "Unable to get buffer memory");
        return;	    
    }    

    // set address if second buffer
    sound_header[1].lpData = sound_header[0].lpData + sound_header[0].dwBufferLength;

    // init header 0
    if (waveInPrepareHeader(sound_in_handle, &sound_header[0], sizeof(WAVEHDR)))
    {
       add_debug("start_sound", "Unable perpare header");
       return;	        
    }    

    // init header 1
    if (waveInPrepareHeader(sound_in_handle, &sound_header[1], sizeof(WAVEHDR)))
    {
       add_debug("start_sound", "Unable perpare header");
       return;	        
    }    

    // tell API we have the first buffer ready
    if (waveInAddBuffer(sound_in_handle, &sound_header[0], sizeof(WAVEHDR)))
    {
       add_debug("start_sound", "Unable add buffer");
       return;	        
    }    

    // tell API we have the second buffer ready
    if (waveInAddBuffer(sound_in_handle, &sound_header[1], sizeof(WAVEHDR)))
    {
       add_debug("start_sound", "Unable add buffer");
       return;	        
    }  

    // start recording
    if (waveInStart(sound_in_handle))
    {
       add_debug("start_sound", "Unable to start sound");
       return;	        
    }    

    //add_debug("start_sound", "Sound start complete");

    sound_running = 1;    
}
/* end of start sound */


/* used to stop sound system */
void stop_sound( void )
{
    sound_running = 0;
    
    waveInReset(sound_in_handle);
    
    //unprepare the headers
    if (waveInPrepareHeader(sound_in_handle, &sound_header[1], sizeof(WAVEHDR)))
         add_debug("stop_sound", "Unable to unprepare");  

    if (waveInPrepareHeader(sound_in_handle, &sound_header[0], sizeof(WAVEHDR)))
         add_debug("stop_sound", "Unable to unprepare");  
    
    // shutdown sound system
    waveInClose(sound_in_handle);

	if (sound_header[0].lpData) VirtualFree(sound_header[0].lpData, 0, MEM_RELEASE);
	
    //add_debug("stop_sound", "Sound Stopped");
}
/* end to start the sound system */


