// --------------------------------------------------------------------------
// Name: sndcpcm.cpp
// Purpose:
// Date: 08/11/1999
// Author: Guilhem Lavaux <lavaux@easynet.fr> (C) 1999, 2000
// CVSID: $Id: sndcpcm.cpp 35650 2005-09-23 12:56:45Z MR $
// wxWindows licence
// --------------------------------------------------------------------------

#include "wx/wxprec.h"

#ifndef WX_PRECOMP
    #include "wx/defs.h"
    #include "wx/debug.h"
    #include "wx/log.h"
#endif

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "wx/mmedia/sndbase.h"
#include "wx/mmedia/sndpcm.h"
#include "wx/mmedia/sndcpcm.h"

wxSoundStreamPcm::wxSoundStreamPcm(wxSoundStream& sndio)
        : wxSoundStreamCodec(sndio)
{
    m_function_in = NULL;
    m_function_out = NULL;
    m_prebuffer = NULL;
    m_prebuffer_size = 0;
    m_best_size = 0;
}

wxSoundStreamPcm::~wxSoundStreamPcm()
{
    if (m_prebuffer)
        delete[] m_prebuffer;
}

wxUint32 wxSoundStreamPcm::GetBestSize() const
{
    return m_best_size;
}

// -----------------------------------------------------------------------
// "Converters" definitions/implementations
// -----------------------------------------------------------------------

#define DEFINE_CONV(name, input_type, output_type, convert) \
static void Convert_##name(const void *buf_in, void *buf_out, wxUint32 len) \
{\
  register input_type src; \
  register const input_type *t_buf_in = (input_type *)buf_in; \
  register output_type *t_buf_out = (output_type *)buf_out; \
\
  while (len > 0) { \
    src = *t_buf_in++; \
    *t_buf_out++ = convert; \
    len -= sizeof(input_type); \
  } \
}

// TODO: define converters for all other formats (32, 24)

DEFINE_CONV(8_8_sign, wxUint8, wxUint8, (src ^ 0x80))

DEFINE_CONV(8_16, wxUint8, wxUint16, (((wxUint16)src) << 8))
DEFINE_CONV(8_16_swap, wxUint8, wxUint16, (src))
DEFINE_CONV(8_16_sign, wxUint8, wxUint16, (((wxUint16)(src ^ 0x80)) << 8))
DEFINE_CONV(8_16_sign_swap, wxUint8, wxUint16, (src ^ 0x80))

DEFINE_CONV(16_8, wxUint16, wxUint8, (wxUint8)(src >> 8))
DEFINE_CONV(16_8_sign, wxUint16, wxUint8, (wxUint8)((src >> 8) ^ 0x80))
DEFINE_CONV(16_swap_8, wxUint16, wxUint8, (wxUint8)(src & 0xff))
DEFINE_CONV(16_swap_8_sign, wxUint16, wxUint8, (wxUint8)((src & 0xff) ^ 0x80))

//DEFINE_CONV(24_8, wxUint32, wxUint8, (wxUint8)(src >> 16))
//DEFINE_CONV(24_8_sig, wxUint32, wxUint8, (wxUint8)((src >> 16) ^ 0x80))

//DEFINE_CONV(32_8, wxUint32, wxUint8, (wxUint8)(src >> 24))

DEFINE_CONV(16_sign, wxUint16, wxUint16, (src ^ 0x8000))
DEFINE_CONV(16_swap, wxUint16, wxUint16, (((src & 0xff) << 8) | ((src >> 8) & 0xff)))
// Problem.
DEFINE_CONV(16_swap_16_sign, wxUint16, wxUint16, ((((src & 0xff) << 8) | ((src >> 8) & 0xff)) ^ 0x80))
// DEFINE_CONV(16_sign_16_swap, wxUint16, wxUint16, ((((src & 0xff) << 8) | ((src >> 8) & 0xff)) ^ 0x8000))
DEFINE_CONV(16_swap_16_sign_swap, wxUint16, wxUint16, (src ^ 0x80))

// -----------------------------------------------------------------------
// Main PCM stream converter table
// -----------------------------------------------------------------------
// Definition
//   XX -> YY
//   XX -> YY sign
//
//   XX swapped -> YY
//   XX swapped -> YY sign
//
//   XX swapped -> YY swapped
//   XX swapped -> YY swapped sign
//
//   XX stereo -> YY mono
//   XX stereo -> YY mono sign
//
//   XX swapped stereo -> YY swapped mono
//   XX swapped stereo -> YY swapped mono sign
//
//   XX swapped stereo -> YY swapped mono
//   XX swapped stereo -> YY swapped mono sign

static wxSoundStreamPcm::ConverterType s_converters[4][3][2] = { 
    {
        {
            NULL,
            Convert_8_8_sign                    /* 8 -> 8 sign */
        },
        {
            NULL,
            NULL
        },
        {
            NULL,
            NULL
        }
    },
    {
        {
            Convert_8_16,                       /* 8 -> 16 */
            Convert_8_16_sign                   /* 8 -> 16 sign */
        },
        {
            Convert_8_16_swap,                  /* 8 -> 16 swapped */
            Convert_8_16_sign_swap              /* 8 -> 16 sign swapped */
        },
        {
            NULL,
            NULL
        }
    },
    {
        {
            Convert_16_8,                       /* 16 -> 8 */
            Convert_16_8_sign                   /* 16 -> 8 sign */
        },
        {
            Convert_16_swap_8,                  /* 16 swapped -> 8 */
            Convert_16_swap_8_sign              /* 16 swapped -> 8 sign */
        },
        {
            NULL,
            NULL 
        },
    },
    
    {
        {
            NULL,                               /* 16 -> 16 */
            Convert_16_sign                     /* 16 -> 16 sign */
        },
        {
            Convert_16_swap,                    /* 16 swapped -> 16 */
            Convert_16_swap_16_sign             /* 16 swapped -> 16 sign */
        },
        {
            NULL,
            Convert_16_swap_16_sign_swap        /* 16 swapped -> 16 sign swapped */
        }
    }
};

// This is the buffer size multiplier. It gives the needed size of the output size.
static float s_converters_multip[] = {1, 2, 0.5, 1};

//
// TODO: Read() and Write() aren't really safe. If you give it a buffer which
// is not aligned on 2, you may crash (See converter.def).
//

wxSoundStream& wxSoundStreamPcm::Read(void *buffer, wxUint32 len)
{
    wxUint32 in_bufsize;

    // We must have a multiple of 2
    len &= 0x01;
    
    if (!m_function_in) {
        m_sndio->Read(buffer, len);
        m_lastcount = m_sndio->GetLastAccess();
        m_snderror = m_sndio->GetError();
        return *this;
    }

    in_bufsize = GetReadSize(len);
    
    if (len <= m_best_size) {
        m_sndio->Read(m_prebuffer, in_bufsize);
        m_snderror  = m_sndio->GetError();
        if (m_snderror != wxSOUND_NOERROR) {
            m_lastcount = 0;
            return *this;
        }
        
        m_function_in(m_prebuffer, buffer, m_sndio->GetLastAccess());
    } else {
        char *temp_buffer;
        
        temp_buffer = new char[in_bufsize];
        m_sndio->Read(temp_buffer, in_bufsize);

        m_snderror =  m_sndio->GetError();
        if (m_snderror != wxSOUND_NOERROR) {
            m_lastcount = 0;
            return *this;
        }
        
        m_function_in(temp_buffer, buffer, m_sndio->GetLastAccess());
        
        delete[] temp_buffer;
    }
    
    m_lastcount = (wxUint32)(m_sndio->GetLastAccess() * m_multiplier_in);
    
    return *this;
}

wxSoundStream& wxSoundStreamPcm::Write(const void *buffer, wxUint32 len)
{
    wxUint32 out_bufsize;
    
    if (!m_function_out) {
        m_sndio->Write(buffer, len);
        m_lastcount = m_sndio->GetLastAccess();
        m_snderror  = m_sndio->GetError();
        return *this;
    }

    out_bufsize = GetWriteSize(len);

    if (len <= m_best_size) {
        out_bufsize = GetWriteSize(len);

        m_function_out(buffer, m_prebuffer, len);
        m_sndio->Write(m_prebuffer, out_bufsize);
        m_snderror  = m_sndio->GetError();
        if (m_snderror != wxSOUND_NOERROR) {
            m_lastcount = 0;
            return *this;
        }
    } else {
        char *temp_buffer;
        
        temp_buffer = new char[out_bufsize];
        m_function_out(buffer, temp_buffer, len);
        
        m_sndio->Write(temp_buffer, out_bufsize);
        m_snderror =  m_sndio->GetError();
        if (m_snderror != wxSOUND_NOERROR) {
            m_lastcount = 0;
            return *this;
        }
        
        delete[] temp_buffer;
    }

    m_lastcount = (wxUint32)(m_sndio->GetLastAccess() / m_multiplier_out);

    return *this;
}

bool wxSoundStreamPcm::SetSoundFormat(const wxSoundFormatBase& format)
{
    wxSoundFormatBase *new_format;
    wxSoundFormatPcm *pcm_format, *pcm_format2;
    
    if (m_sndio->SetSoundFormat(format)) {
        m_function_out = NULL;
        m_function_in = NULL;
        return true;
    }
    if (format.GetType() != wxSOUND_PCM) {
        m_snderror = wxSOUND_INVFRMT;
        return false;
    }
    if (m_sndformat)
        delete m_sndformat;
    
    new_format = m_sndio->GetSoundFormat().Clone();
    pcm_format = (wxSoundFormatPcm *)&format;
    pcm_format2 = (wxSoundFormatPcm *)new_format;

#if 0
    // ----------------------------------------------------
    // Test whether we need to resample
    if (pcm_format->GetSampleRate() != pcm_format2->GetSampleRate()) {
        wxUint32 src_rate, dst_rate;

        src_rate = pcm_format->GetSampleRate();
        dst_rate = pcm_format2->GetSampleRate();
        m_needResampling = true;
        if (src_rate < dst_rate)
            m_expandSamples = true;
        else
            m_expandSamples = false;
        m_pitch = (src_rate << FLOATBITS) / dst_rate;
    }
#endif
    // ----------------------------------------------------
    // Select table to use:
    //     * 8 bits -> 8 bits
    //     * 16 bits -> 8 bits
    //     * 8 bits -> 16 bits
    //     * 16 bits -> 16 bits

    int table_no, table_no2;
    int i_sign, i_swap;
    
    switch (pcm_format->GetBPS()) {
        case 8:
            table_no = 0;
            break;
        case 16:
            table_no = 1;
            break;
        default:
            // TODO: Add something here: error, log, ...
            return false;
    }
    switch (pcm_format2->GetBPS()) {
        case 8:
            table_no2 = 0;
            break;
        case 16:
            table_no2 = 1;
            break;
        default:
            // TODO: Add something here: error, log, ...
            return false;
    }
    
    if (pcm_format2->Signed() != pcm_format->Signed())
        i_sign = 1;
    else
        i_sign = 0;

#define MY_ORDER wxBYTE_ORDER
#if wxBYTE_ORDER == wxLITTLE_ENDIAN
#define OTHER_ORDER wxBIG_ENDIAN
#else
#define OTHER_ORDER wxLITTLE_ENDIAN
#endif

    // --------------------------------------------------------
    // Find the good converter !

    if (pcm_format->GetOrder() == OTHER_ORDER) {
        if (pcm_format->GetOrder() == pcm_format2->GetOrder())
            i_swap = 2;
        else
            i_swap = 1;
    } else {
        if (pcm_format->GetOrder() == pcm_format2->GetOrder())
            i_swap = 0;
        else
            i_swap = 1;
    }

    m_function_out = s_converters[table_no*2+table_no2][i_swap][i_sign];
    m_function_in  = s_converters[table_no2*2+table_no][i_swap][i_sign];
    m_multiplier_out = s_converters_multip[table_no*2+table_no2];
    m_multiplier_in  = s_converters_multip[table_no2*2+table_no2];

    if (m_prebuffer)
        delete[] m_prebuffer;

    // We try to minimize the need for dynamic memory allocation by preallocating a buffer. But
    // to be sure it will be efficient we minimize the best size.
    if (m_multiplier_in < m_multiplier_out) {
        m_prebuffer_size = (wxUint32)(m_sndio->GetBestSize() *
                                      m_multiplier_out);
        m_best_size = (wxUint32)(m_sndio->GetBestSize() *
                                 m_multiplier_in);
    } else {
        m_prebuffer_size = (wxUint32)(m_sndio->GetBestSize() *
                                      m_multiplier_in);
        m_best_size = (wxUint32)(m_sndio->GetBestSize() *
                                 m_multiplier_out);
    }
    
    m_prebuffer = new char[m_prebuffer_size];
    
    bool SetSoundFormatReturn;

    SetSoundFormatReturn = m_sndio->SetSoundFormat(*new_format);
    wxASSERT( SetSoundFormatReturn );
    wxUnusedVar( SetSoundFormatReturn );
    
    m_sndformat = new_format;
    return true;
}

wxUint32 wxSoundStreamPcm::GetWriteSize(wxUint32 len) const
{
    // For the moment, it is simple but next time it will become more complicated
    // (Resampling)
    return (wxUint32)(len * m_multiplier_out);
}

wxUint32 wxSoundStreamPcm::GetReadSize(wxUint32 len) const
{
    return (wxUint32)(len / m_multiplier_in);
}

// Resampling engine. NOT FINISHED and NOT INCLUDED but this is a first DRAFT.

#if 0

#define FLOATBITS 16
#define INTBITS 16
#define FLOATMASK 0xffff
#define INTMASK 0xffff0000

void ResamplingShrink_##DEPTH##(const void *source, void *destination, wxUint32 len)
{
    wxUint##DEPTH## *source_data, *dest_data;
    wxUint32 pos;

    source_data = (wxUint##DEPTH## *)source;
    dest_data   = (wxUint##DEPTH## *)destination;
    
    pos = m_saved_pos;
    while (len > 0) {
        // Increment the position in the input buffer
        pos += m_pitch;
        if (pos & INTMASK) {
            pos &= FLOATMASK;
            
            *dest_data ++ = *source_data;
        }
        len--;
        source_data++;
    }
    m_saved_pos = pos;
}
#endif
