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

#include "stdafx.h"
#include "audio_waveout.hpp"

_AUDIO_NAMESPACE_START_

void
audio_waveout::init_(void)
{
    ZeroMemory((LPVOID)&wave_format, sizeof(WAVEFORMATEX));
    wave_format.cbSize = sizeof(WAVEFORMATEX);
    waveout_handle = 0;
    playthread_id = 0;
    wakeup_playthread = 0;
    buf_secs = _AUDIO_DEFAULT_WAVEOUTBUFSECS;
    status = WAVEOUT_NOTREADY;
}

void 
audio_waveout::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_waveout::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;

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

    /* Initializes headers */
    for (unsigned int i = 0; i < buffers; ++i)
    {
        /* Sets the correct base address and length for the little buffer */
        wave_headers[i].dwBufferLength = mb_size / buffers;
        wave_headers[i].lpData = (LPSTR) buf_addr;

        /* Unsets the WHDR_DONE flag */
        wave_headers[i].dwFlags &= ~WHDR_DONE;

        /* Sets the WAVEHDR user data with an unique little buffer ID# */
        wave_headers[i].dwUser = (unsigned int)i;

        /* Increments little buffer base address */
        buf_addr += buf_sz;
    }
}

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

    /* If there is no memory for memory or headers, throw error */
    if ((!wave_headers) || (!main_buffer) || (!waveout_handle))
    {
        /* TODO: throw error! */
    }

    for (unsigned int i = 0; i < buffers; ++i)
    {
        err = waveOutPrepareHeader(waveout_handle, &wave_headers[i], sizeof(WAVEHDR));
        if (err != MMSYSERR_NOERROR)
            error = true;
    }

    if (error)
    {
        /* TODO: throw error indicating which header i-th is errorneous */
    }
}

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

    /* If there is no memory for memory or headers, throw error */
    if ((!wave_headers) || (!main_buffer) || (!waveout_handle))
    {
        /* TODO: throw error! */
    }

    for (unsigned int i = 0; i < buffers; ++i)
    {
        err = waveOutUnprepareHeader(waveout_handle, &wave_headers[i], sizeof(WAVEHDR));
        if (err != MMSYSERR_NOERROR)
            error = true;
    }

    if (error)
    {
        /* TODO: throw error indicating which header i-th is errorneous */
    }
}

void 
audio_waveout::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_waveout::open(void)
{
    MMRESULT err;
    HANDLE playthread_handle = 0;

    /* Checkin the status of the object */
    if (status != WAVEOUT_NOTREADY)
    {
        /* TODO: throw error */
    }

    /* Creating the EVENT object that will be signaled when
       the playing thread has to wake up */
    wakeup_playthread = CreateEvent(0, FALSE, FALSE, 0);
    if (!wakeup_playthread)
    {
        status = WAVEOUT_ERR;
        /* 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 */
    playthread_handle = CreateThread(NULL,
                                     0,
                                     audio_waveout::playing_procedure,
                                     (PVOID)this,
                                     0,
                                     &playthread_id);
    /* Checking thread handle */
    if (!playthread_handle)
    {
        /* Updating status */
        status = WAVEOUT_ERR;
        /* 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(playthread_handle);

    /* Reset the `audio_source' to the start position */
    audio_buf.set_position_start();
    /* Opens the WAVE_OUT device */
    err = waveOutOpen(&waveout_handle,
                      WAVE_MAPPER,
                      &wave_format,
                      playthread_id,
                      0,
                      CALLBACK_THREAD | WAVE_ALLOWSYNC);
    if (err != MMSYSERR_NOERROR)
    {
        MessageBox(0, _T("waveOutOpen Error"), 0, 0);
        /* TODO: throw error */
    }

    status = WAVEOUT_READY;
}

void 
audio_waveout::play(void)
{
    MMRESULT err;
    unsigned int i;

    if (!main_buffer)
    {
        /* TODO; throw error, or assert */
        return;
    }

    /* If the status is PAUSED, we have to resume the audio playing */
    if (status == WAVEOUT_PAUSED)
    {
        /* Updates status */
        status = WAVEOUT_PLAYING;
        /* Tells to the driver to resume audio playing */
        waveOutRestart(waveout_handle);
        /* Wakeup playing thread */
        SetEvent(wakeup_playthread);
        return;
    } /* if status == WAVEOUT_PAUSED */

    if (status != WAVEOUT_READY)
        return;

    /* Prepares WAVEHDR structures */
    prep_headers_();
    /* Sets correct status */
    status = WAVEOUT_PLAYING;
    /* Reads the audio from the start */
    //audio_buf.set_position_start();

    /* Reads the first N bytes from the audio buffer, where N = the total
       size of all little buffers */
    audio_buf.read(main_buffer, mb_size);

    /* Wakeup the playing thread */
    SetEvent(wakeup_playthread);

    /* Sends all the little buffers to the audio driver, so it can play
       the sound data */
    for (i = 0; i < buffers; ++i)
    {
        err = waveOutWrite(waveout_handle, &wave_headers[i], sizeof(WAVEHDR));
        if (err != MMSYSERR_NOERROR)
        {
            MessageBox(0, _T("waveOutWrite Error"), 0, 0);
            /* TODO: throw error */
        }
    }
}

void 
audio_waveout::pause(void)
{
    MMRESULT err;

    /* If the waveout object is not playing audio, do nothing */
    if (status == WAVEOUT_PLAYING)
    {
        /* Updating status */
        status = WAVEOUT_PAUSED;
        /* Tells to audio driver to pause audio */
        err = waveOutPause(waveout_handle);
        if (err != MMSYSERR_NOERROR)
        {
            MessageBox(0, _T("waveOutPause Error"), 0, 0);
            /* TODO: throw error */
        }
    }
}

void 
audio_waveout::stop(void)
{
    MMRESULT err;

    /* Checks the current status */
    if ((status != WAVEOUT_PLAYING) &&
        (status != WAVEOUT_FLUSHING) &&
        (status != WAVEOUT_PAUSED))
    {
        /* Do nothing */
        return;
    }

    /* Sets a new status */
    status = WAVEOUT_STOP;
    /* Flushes pending audio datas */
    err = waveOutReset( waveout_handle );
    if (err != MMSYSERR_NOERROR)
    {
        MessageBox(0, _T("err WaveOutReset.\n"),_T("ERROR"), 0);
        /* TODO: throw error */
    }

    /* Sets the start position of the audio buffer */
    audio_buf.set_position_start();
    /* Cleans little buffers */
    unprep_headers_();
    init_headers_();
    /* Refreshes the status */
    status = WAVEOUT_READY;
}

void
audio_waveout::close(void)
{
    MMRESULT err;

    /* If the `wave_out' object is playing audio, or it is in paused state,
       we have to call the `stop' member function, to flush pending buffers */
    if ((status == WAVEOUT_PLAYING) || (status== WAVEOUT_PAUSED))
    {
        stop();
    }

    /* When we have flushed all pending buffers, the wave out handle can be successfully closed */
    err = waveOutClose(waveout_handle);
    if (err != MMSYSERR_NOERROR)
    {
        MessageBox(0, _T("waveOutClose Error"), 0, 0);
        /* TODO: throw error */
    }

    free_buffers_mem_();
}

DWORD WINAPI 
audio_waveout::playing_procedure(LPVOID arg)
{
    MSG msg;
    WAVEHDR *phdr;
    MMRESULT err;
    audio_waveout *_this = (audio_waveout *)arg;
    unsigned int read_size;

    /* 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_playthread)
        WaitForSingleObject(_this->wakeup_playthread, INFINITE);

    /* Entering main polling loop */
    while (GetMessage(&msg, 0, 0, 0))
    {
        switch (msg.message)
        {
            case MM_WOM_DONE:
                phdr = (WAVEHDR *)msg.lParam;

                /* If the status of the `wave_out' object is different
                   than playing, then the thread can go to sleep */
                if ((_this->status != WAVEOUT_PLAYING) &&
                    (_this->status != WAVEOUT_FLUSHING) &&
                    (_this->wakeup_playthread))
                {
                    WaitForSingleObject(_this->wakeup_playthread, INFINITE);
                }

                /* The playing thread doesn't have to sleep, so let's checking
                   first if the little buffer has been sent to the audio driver
                   (it has the WHDR_DONE flag). If it is, we can read new audio
                   datas from the `audio_producer' object, refill the little buffer,
                   and resend it to the driver with waveOutWrite( ) API */
                if (phdr->dwFlags & WHDR_DONE)
                {
                    if (_this->status == WAVEOUT_PLAYING)
                    {
                        /* Here the thread is still playing a sound, so it can
                           read new audio data from the `audio_producer' object */
                        read_size = _this->audio_buf.read((BYTE *)phdr->lpData,
                                                          phdr->dwBufferLength);
                    } else
                        read_size = 0;

                    /* If the `audio_producer' object, has produced some
                       audio data, so `read_size' will be > 0 */
                    if (read_size)
                    {
                        /* Adjusts the correct effectively read size */
                        phdr->dwBufferLength = read_size;

                        /* Before sending the little buffer to the driver,
                           we have to remove the `WHDR_DONE' flag, because
                           the little buffer now contain new audio data that have to be played */
                        phdr->dwFlags &= ~WHDR_DONE;

                        /* Plays the sound of the little buffer */
                        err = waveOutWrite(_this->waveout_handle,
                                           phdr,
                                           sizeof(WAVEHDR));

                        /* Checking if any error has occured */
                        if (err != MMSYSERR_NOERROR)
                        {
                            MessageBox(0, _T("waveOutWrite Error"), 0, 0);
                            /* TODO: throw error */
                        }
                    }
                    else
                    {
                        /* Here `read_size' is 0, so the `audio_producer' object,
                           doesn't have any sound data to produce anymore. So,
                           now we have to see the little buffer #ID to establishing what to do */
                        if (phdr->dwUser == 0)
                        {
                            /* Here `read_size' is 0, and the buffer user data
                               contain 0, so this is the first of N little
                               buffers that came back with `WHDR_DONE' flag;
                               this means that this is the last little buffer
                               in which we have to read data to; so we can
                               _STOP_ reading data from the `audio_producer'
                               object: doing this is accomplished just setting
                               the current status as "WAVEOUT_FLUSHING" */
                            _this->status = WAVEOUT_FLUSHING;
                        }
                        else if (phdr->dwUser == (_this->buffers - 1))
                        {
                            /* Here `read_size' and the buffer user data, that
                               contain a buffer ID#, is equal to the number of
                               the total buffers - 1. This means that this is
                               the _LAST_ little buffer that has been played by
                               the audio driver. We can STOP the `wave_out'
                               object now, or restart the sound playing, if we have a infinite loop */
                            _this->stop();

                            /* Let the thread go to sleep */
                            if (_this->audio_buf.play_finished)
                                _this->audio_buf.play_finished();

                            if (_this->wakeup_playthread)
                                WaitForSingleObject(_this->wakeup_playthread,
                                                    INFINITE);

                        } /* if (phdr->dwUser == (_this->buffers - 1)) */
                    } /* if read_size != 0 */
                } /* (phdr->dwFlags & WHDR_DONE) */
                break; /* end case */
            case MM_WOM_CLOSE:
                /* The thread can exit now */
                return 0;
                break;
            case MM_WOM_OPEN:
                /* Do nothing */
                break;
        } /* end switch(msg.message) */
    } /* end while(GetMessage(...)) */

    return 0;
}

_AUDIO_NAMESPACE_END_
