/* PROJECT:         ReactOS sndrec32
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            base/applications/sndrec32/audio_wavein.cpp
 * PURPOSE:         Sound recording
 * PROGRAMMERS:     Marco Pagliaricci (irc: rendar)
 */



#include "stdafx.h"
#include "audio_wavein.hpp"



_AUDIO_NAMESPACE_START_


void
audio_wavein::init_( void )
{
    ZeroMemory(( LPVOID ) &wave_format, 
                    sizeof( WAVEFORMATEX ));

    wave_format.cbSize = sizeof( WAVEFORMATEX );

    wavein_handle = 0;
    recthread_id = 0;
    wakeup_recthread = 0;

    data_flushed_event = 0;

    buf_secs = _AUDIO_DEFAULT_WAVEINBUFSECS;


    status = WAVEIN_NOTREADY;
}


void
audio_wavein::alloc_buffers_mem_( unsigned int buffs, float secs )
{


    unsigned int 
        onebuf_size = 0, tot_size = 0;


    //
    // Release old memory
    //

    if ( main_buffer )
        delete[] main_buffer;


    if ( wave_headers )
        delete[] wave_headers;



    //
    // Calcs size of the buffers
    //

    onebuf_size = ( unsigned int )
        (( float )aud_info.byte_rate() * secs ); 


    tot_size = onebuf_size * buffs;

    
    
    
    //
    // Allocs memory for the audio buffers
    //

    main_buffer = new BYTE [ tot_size ];



    //
    // Allocs memory for the `WAVEHDR' structures.
    //

    wave_headers = ( WAVEHDR * ) 
        new BYTE [ sizeof( WAVEHDR ) * buffs ];



    //
    // Zeros memory.
    //

    ZeroMemory( main_buffer, tot_size );

    ZeroMemory( wave_headers, 
        sizeof( WAVEHDR ) * buffs );


    //
    // Updates total size of the buffers.
    //

    mb_size = tot_size;

}


void 
audio_wavein::free_buffers_mem_( void )
{


    //
    // Frees memory
    //

    if ( main_buffer )
        delete[] main_buffer;


    if ( wave_headers )
        delete[] wave_headers;


    main_buffer = 0;
    wave_headers = 0;

}


void 
audio_wavein::init_headers_( void )
{



    //
    // If there is no memory for memory or
    // headers, simply return.
    //

    if (( !wave_headers ) || ( !main_buffer ))
        return;


    //
    // This is the size for one buffer
    //

    DWORD buf_sz = mb_size / buffers;



    //
    // This is the base address for one buffer
    //
    
    BYTE * buf_addr = main_buffer;


    //
    // Initializes headers.
    //

    for ( unsigned int i = 0; i < buffers; ++i )
    {
        wave_headers[ i ].dwBufferLength = mb_size / buffers;
        wave_headers[ i ].lpData = ( LPSTR ) buf_addr;

        buf_addr += buf_sz;
    }

}


void 
audio_wavein::prep_headers_( void )
{
    MMRESULT err;
    bool error = false;


    //
    // If there is no memory for memory or
    // headers, throw error.
    //

    if (( !wave_headers ) 
        || ( !main_buffer ) || ( !wavein_handle ))
    {} //TODO: throw error!



    for ( unsigned int i = 0; i < buffers; ++i )
    {
        err = waveInPrepareHeader( wavein_handle, 
                    &wave_headers[ i ], sizeof( WAVEHDR ));


        if ( err != MMSYSERR_NOERROR )
            error = true;

    }
    

    if ( error )
        MessageBox( 0, TEXT("waveInPrepareHeader Error."), 0, 0 );



}

void 
audio_wavein::unprep_headers_( void )
{
    MMRESULT err;
    bool error = false;



    //
    // If there is no memory for memory or
    // headers, throw error.
    //

    if (( !wave_headers ) 
        || ( !main_buffer ) || ( !wavein_handle ))
    {} //TODO: throw error!



    for ( unsigned int i = 0; i < buffers; ++i )
    {
        err = waveInUnprepareHeader( wavein_handle, 
                    &wave_headers[ i ], sizeof( WAVEHDR ));


        if ( err != MMSYSERR_NOERROR )
            error = true;

    }
    

    if ( error )
        MessageBox( 0, TEXT("waveInUnPrepareHeader Error."), 0, 0 );

}


void 
audio_wavein::add_buffers_to_driver_( void )
{
    MMRESULT err;
    bool error = false;



    //
    // If there is no memory for memory or
    // headers, throw error.
    //

    if (( !wave_headers ) 
        || ( !main_buffer ) || ( !wavein_handle ))
    {} //TODO: throw error!




    for ( unsigned int i = 0; i < buffers; ++i )
    {
        err = waveInAddBuffer( wavein_handle, 
                &wave_headers[ i ], sizeof( WAVEHDR ));


        if ( err != MMSYSERR_NOERROR )
            error = true;

    }
    

    if ( error )
        MessageBox( 0, TEXT("waveInAddBuffer Error."), 0, 0 );

}



void
audio_wavein::close( void ) 
{




    //
    // If wavein object is already in the status
    // NOTREADY, nothing to do. 
    //

    if ( status == WAVEIN_NOTREADY )
        return;



    //
    // If the wavein is recording,
    // then stop recording and close it.
    //
    
    if ( status == WAVEIN_RECORDING )
        stop_recording();


    //
    // Updating status.
    //

    status = WAVEIN_NOTREADY;




    //
    // Wakeing up recording thread, so it
    // can receive the `MM_WIM_CLOSE' message
    // then dies.
    //
    if ( wakeup_recthread )
        SetEvent( wakeup_recthread );



    //
    // Closing wavein stream
    //

    while (( waveInClose( wavein_handle )) 
                    != MMSYSERR_NOERROR ) Sleep( 1 );



    //
    // Release buffers memory.
    //

    free_buffers_mem_();


    //
    // Re-initialize variables to the
    // initial state.
    //

    init_();
    
}


void
audio_wavein::open( void )
{

    MMRESULT err;
    HANDLE recthread_handle = 0;


    //
    // Checkin the status of the object
    //

    if ( status != WAVEIN_NOTREADY )
    {} //TODO: throw error



    //
    // Creating the EVENT object that will be signaled
    // when the recording thread has to wake up.
    //

    wakeup_recthread = 
        CreateEvent( 0, FALSE, FALSE, 0 );


    data_flushed_event = 
        CreateEvent( 0, FALSE, FALSE, 0 );



    if (( !wakeup_recthread ) || ( !data_flushed_event ))
    {


        status = WAVEIN_ERR;

        MessageBox( 0, TEXT("Thread Error."), 0, 0 );

        //TODO: throw error
    }



    //
    // Inialize buffers for recording audio 
    // data from the wavein audio line.
    //

    alloc_buffers_mem_( buffers, buf_secs );
    init_headers_();






    //
    // Sound format that will be captured by wavein
    //

    wave_format.wFormatTag = WAVE_FORMAT_PCM;

    wave_format.nChannels = aud_info.channels();
    wave_format.nSamplesPerSec = aud_info.sample_rate();
    wave_format.wBitsPerSample = aud_info.bits();
    wave_format.nBlockAlign = aud_info.block_align();
    wave_format.nAvgBytesPerSec = aud_info.byte_rate();



    //
    // Creating the recording thread
    //

    recthread_handle = 
        CreateThread( NULL, 
                      0, 
                      audio_wavein::recording_procedure, 
                      ( PVOID ) this, 
                      0, 
                      &recthread_id 
            );

    

    //
    // Checking thread handle
    //

    if ( !recthread_handle )
    {

        //
        // Updating status
        //

        status = WAVEIN_ERR;

        MessageBox( 0, TEXT("Thread Error."), 0, 0 );
        //TODO: throw error

    }


    //
    // We don't need the thread handle anymore,
    // so we can close it from now. (We'll just
    // need the thread ID for the `waveInOpen' API)
    //

    CloseHandle( recthread_handle );



    //
    // Opening audio line wavein
    //

    err = waveInOpen( &wavein_handle, 
                      0, 
                      &wave_format, 
                      recthread_id, 
                      0, 
                      CALLBACK_THREAD 
            );


    if ( err != MMSYSERR_NOERROR ) 
    {


        //
        // Updating status
        //

        status = WAVEIN_ERR;

        if ( err == WAVERR_BADFORMAT )
            MessageBox( 0, TEXT("waveInOpen Error"), 0, 0 );


        //TODO: throw error
    }


    //
    // Update object status
    //

    status = WAVEIN_READY;



    //
    // Now `audio_wavein' object is ready
    // for audio recording!
    //
}



void
audio_wavein::start_recording( void )
{

    MMRESULT err;
    BOOL ev;



    if (( status != WAVEIN_READY ) 
                && ( status != WAVEIN_STOP ))
    {} //TODO: throw error




    //
    // Updating to the recording status
    //

    status = WAVEIN_RECORDING;




    //
    // Let's prepare header of type WAVEHDR that
    // we will pass to the driver with our
    // audio informations, and buffer informations.
    //
    
    prep_headers_();



    //
    // The waveInAddBuffer function sends an input buffer 
    // to the given waveform-audio input device. 
    // When the buffer is filled, the application is notified.
    //

    add_buffers_to_driver_();





    //
    // Signaling event for waking up
    // the recorder thread.
    //
    
    ev = SetEvent( wakeup_recthread );


    if ( !ev ) 
    {


        MessageBox( 0, TEXT("Event Error."), 0, 0 );

    }
    

    //
    // Start recording
    //

    
    err = waveInStart( wavein_handle );


    if ( err != MMSYSERR_NOERROR )
    {

        //
        // Updating status
        //

        status = WAVEIN_ERR;

        MessageBox( 0, TEXT("waveInStart Error."), 0, 0 );


        //TODO: throw error

    }

}



void
audio_wavein::stop_recording( void )
{
        

    MMRESULT err;
    DWORD wait;


    if ( status != WAVEIN_RECORDING )
        return;


    
    status = WAVEIN_FLUSHING;


    //
    // waveInReset will make all pending buffer as done.
    //

    err = waveInReset( wavein_handle );


    if ( err != MMSYSERR_NOERROR )
    {

        //TODO: throw error

        MessageBox( 0, TEXT("waveInReset Error."), 0, 0 );



    }


    if ( data_flushed_event )
        wait = WaitForSingleObject( 
                        data_flushed_event, INFINITE 
                    );



    


    
    //
    // Stop recording.
    //

    err = waveInStop( wavein_handle );


    if ( err != MMSYSERR_NOERROR )
    {

        //TODO: throw error

        MessageBox( 0, TEXT("waveInStop Error."), 0, 0 );



    }


    //
    // The waveInUnprepareHeader function cleans up the 
    // preparation performed by the waveInPrepareHeader function. 
    //

    unprep_headers_();





    





    status = WAVEIN_STOP;

}



DWORD WINAPI 
audio_wavein::recording_procedure( LPVOID arg )
{


    MSG msg;
    WAVEHDR * phdr;
    DWORD wait;
    audio_wavein * _this = ( audio_wavein * ) arg;

    


    //
    // Check the arg pointer
    //

    if ( _this == 0 )
        return 0;

    
    
    //
    // The thread can go to sleep for now.
    // It will be wake up only when there is audio data
    // to be recorded.
    //

    if ( _this->wakeup_recthread )
        wait = WaitForSingleObject( 
                        _this->wakeup_recthread, INFINITE 
                    );





    
    //
    // If status of the `audio_wavein' object 
    // is not ready or recording the thread can exit.
    //

    if (( _this->status != WAVEIN_READY ) && 
                ( _this->status != WAVEIN_RECORDING ))
        return 0;


    




    //
    // Entering main polling loop
    //

    while ( GetMessage( &msg, 0, 0, 0 )) 	
    {	

        switch ( msg.message )
        {
                
            case MM_WIM_DATA:
            
                phdr = ( WAVEHDR * ) msg.lParam;

                if (( _this->status == WAVEIN_RECORDING ) 
                            || ( _this->status == WAVEIN_FLUSHING ))
                {


                    if ( phdr->dwFlags & WHDR_DONE )
                    {

                        //
                        // Flushes recorded audio data to 
                        // the `audio_receiver' object.
                        //

                        _this->audio_rcvd.audio_receive(
                                ( unsigned char * )phdr->lpData, 
                                phdr->dwBytesRecorded 
                            );

                        
                        //
                        // Updating `audio_receiver' total
                        // bytes received _AFTER_ calling
                        // `audio_receive' function.
                        //

                        _this->audio_rcvd.bytes_received += 
                                        phdr->dwBytesRecorded;
                    }
    

                            
                    //
                    // If status is not flushing data, then
                    // we can re-add the buffer for reusing it.
                    // Otherwise, if we are flushing pending data,
                    // we cannot re-add buffer because we don't need
                    // it anymore
                    //

                    if ( _this->status != WAVEIN_FLUSHING )
                    {

                        //
                        // Let the audio driver reuse the buffer
                        //

                        waveInAddBuffer( _this->wavein_handle, 
                                            phdr, sizeof( WAVEHDR ));


                    } else {

                        //
                        // If we are flushing pending data, we have
                        // to prepare to stop recording.
                        // Set WAVEHDR flag to 0, and fires the event
                        // `data_flushed_event', that will wake up
                        // the main thread that is sleeping into
                        // wavein_in::stop_recording() member function,
                        // waiting the last `MM_WIM_DATA' message that
                        // contain pending data.
                        //

                        phdr->dwFlags = 0;

                        SetEvent( _this->data_flushed_event );


                        //
                        // The recording is gooing to stop, so the
                        // recording thread can go to sleep!
                        //

                        wait = WaitForSingleObject( 
                                    _this->wakeup_recthread, INFINITE );
                            
                    }


                }//if WAVEIN_RECORDING || WAVEIN_FLUSHING

                break;


            

                        




            case MM_WIM_CLOSE:

                //
                // The thread can exit now.
                //

                return 0;

                break;



        }  //end switch( msg.message )
        
    }  //end while( GetMessage( ... ))

    return 0;				
}






_AUDIO_NAMESPACE_END_