//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
//==========================================================================;
//
//  pcmconv.c
//
//  Description:
//      This module contains conversion routines for PCM data.
//
//==========================================================================;

#include <windows.h>
#include <mmddk.h>
#include <mmreg.h>
#include "msacm.h"
#include "msacmdrv.h"
#include "acmp.h"
#include "pcm.h"

#define HUGE_T  UNALIGNED

//--------------------------------------------------------------------------;
//
//  LPBYTE pcmReadSample_dddsss
//
//  Description:
//      These functions read a sample from the source stream in the format
//      specified by 'sss' and return the data in the destination 'ddd'
//      format in *pdw.
//
//      For example, the pcmReadSample_M16S08 function reads source data
//      that is in Stereo 8 Bit format and returns an appropriate sample
//      for the destination as Mono 16 Bit.
//
//  Arguments:
//      LPBYTE pb:
//
//      LPDWORD pdw:
//
//  Return (LPBYTE):
//
//
//--------------------------------------------------------------------------;

LPBYTE FNLOCAL pcmReadSample_M08M08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    *(LPBYTE)pdw = ((BYTE HUGE *)pb)[0];

    return ((LPBYTE)&((BYTE HUGE *)pb)[1]);
} // pcmReadSample_M08M08()

LPBYTE FNLOCAL pcmReadSample_S08M08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD    w;

    w = (WORD)((BYTE HUGE *)pb)[0];

    *(LPWORD)pdw = (w << 8) | w;

    return ((LPBYTE)&((BYTE HUGE *)pb)[1]);
} // pcmReadSample_S08M08()

LPBYTE FNLOCAL pcmReadSample_M16M08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    *(LPWORD)pdw = (WORD)(((BYTE HUGE *)pb)[0] ^ (BYTE)0x80) << 8;

    return ((LPBYTE)&((BYTE HUGE *)pb)[1]);
} // pcmReadSample_M16M08()

LPBYTE FNLOCAL pcmReadSample_S16M08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD    w;

    w = (WORD)(((BYTE HUGE *)pb)[0] ^ (BYTE)0x80) << 8;

    *pdw = MAKELONG(w, w);

    return ((LPBYTE)&((BYTE HUGE *)pb)[1]);
} // pcmReadSample_S16M08()


//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

LPBYTE FNLOCAL pcmReadSample_M08S08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD            w;
    int             n;

    w = ((WORD HUGE_T *)pb)[0] ^ 0x8080;

    n = (int)(char)w + (int)(char)(w >> 8);

    if (n > 127)
    {
        *(LPBYTE)pdw = 255;
    }
    else if (n < -128)
    {
        *(LPBYTE)pdw = 0;
    }
    else
    {
        *(LPBYTE)pdw = (BYTE)n ^ (BYTE)0x80;
    }

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_M08S08()

LPBYTE FNLOCAL pcmReadSample_S08S08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    *(LPWORD)pdw = ((WORD HUGE_T *)pb)[0];

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_S08S08()

LPBYTE FNLOCAL pcmReadSample_M16S08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    LONG            l;
    WORD            w;

    w = ((WORD HUGE_T *)pb)[0] ^ 0x8080;

    l = (long)(short)(w << 8) + (long)(short)(w & 0xFF00);

    if (l > 32767)
    {
        *(LPWORD)pdw = 32767;
    }
    else if (l < -32768)
    {
        *(LPWORD)pdw = (WORD)-32768;
    }
    else
    {
        *(LPWORD)pdw = LOWORD(l);
    }

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_M16S08()

LPBYTE FNLOCAL pcmReadSample_S16S08
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD    w;

    w = ((WORD HUGE_T *)pb)[0] ^ 0x8080;

    *pdw = MAKELONG(w << 8, w & 0xFF00);

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_S16S08()


//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

LPBYTE FNLOCAL pcmReadSample_M08M16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    BYTE            b;

    b = (BYTE)(((WORD HUGE_T *)pb)[0] >> 8);

    *(LPBYTE)pdw = b ^ (BYTE)0x80;

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_M08M16()

LPBYTE FNLOCAL pcmReadSample_S08M16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD    w;

    w = ((WORD HUGE_T *)pb)[0] & 0xFF00;

    *(LPWORD)pdw = (w | (w >> 8)) ^ 0x8080;

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_S08M16()

LPBYTE FNLOCAL pcmReadSample_M16M16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    *(LPWORD)pdw = ((WORD HUGE_T *)pb)[0];

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_M16M16()

LPBYTE FNLOCAL pcmReadSample_S16M16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    WORD    w;

    w = ((WORD HUGE_T *)pb)[0];

    *pdw = MAKELONG(w, w);

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmReadSample_S16M16()


//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

LPBYTE FNLOCAL pcmReadSample_M08S16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    DWORD           dw;
    LONG            l;

    dw = ((DWORD HUGE_T *)pb)[0];

    l = (long)(short)LOWORD(dw) + (long)(short)HIWORD(dw);

    if (l > 32767)
    {
        *(LPBYTE)pdw = 255;
    }
    else if (l < -32768)
    {
        *(LPBYTE)pdw = 0;
    }
    else
    {
        *(LPBYTE)pdw = (BYTE)(l >> 8) ^ (BYTE)0x80;
    }

    return ((LPBYTE)&((DWORD HUGE_T *)pb)[1]);
} // pcmReadSample_M08S16()

LPBYTE FNLOCAL pcmReadSample_S08S16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    DWORD   dw;
    WORD    w1;
    WORD    w2;

    dw = ((DWORD HUGE_T *)pb)[0];

    w1 = LOWORD(dw) >> 8;
    w2 = HIWORD(dw) & 0xFF00;

    *(LPWORD)pdw = (w1 | w2) ^ 0x8080;

    return ((LPBYTE)&((DWORD HUGE_T *)pb)[1]);
} // pcmReadSample_S08S16()

LPBYTE FNLOCAL pcmReadSample_M16S16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    DWORD           dw;
    LONG            l;


    dw = ((DWORD HUGE_T *)pb)[0];

    l = (long)(short)LOWORD(dw) + (long)(short)HIWORD(dw);

    if (l > 32767)
    {
        *(LPWORD)pdw = 32767;
    }
    else if (l < -32768)
    {
        *(LPWORD)pdw = (WORD)-32768;
    }
    else
    {
        *(LPWORD)pdw = LOWORD(l);
    }

    return ((LPBYTE)&((DWORD HUGE_T *)pb)[1]);
} // pcmReadSample_M16M16()

LPBYTE FNLOCAL pcmReadSample_S16S16
(
    LPBYTE              pb,
    LPDWORD             pdw
)
{
    *pdw = ((DWORD HUGE_T *)pb)[0];

    return ((LPBYTE)&((DWORD HUGE_T *)pb)[1]);
} // pcmReadSample_S16S16()


//--------------------------------------------------------------------------;
//
//  LPBYTE pcmWriteSample_ddd
//
//  Description:
//
//
//  Arguments:
//      LPBYTE pb:
//
//      DWORD dw:
//
//  Return (LPBYTE):
//
//
//--------------------------------------------------------------------------;

LPBYTE FNLOCAL pcmWriteSample_M08
(
    LPBYTE              pb,
    DWORD               dw
)
{
    ((BYTE HUGE *)pb)[0] = (BYTE)dw;

    return ((LPBYTE)&((BYTE HUGE *)pb)[1]);
} // pcmWriteSample_M08()

LPBYTE FNLOCAL pcmWriteSample_S08
(
    LPBYTE              pb,
    DWORD               dw
)
{
    ((WORD HUGE_T *)pb)[0] = LOWORD(dw);

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmWriteSample_S08()

LPBYTE FNLOCAL pcmWriteSample_M16
(
    LPBYTE              pb,
    DWORD               dw
)
{
    ((WORD HUGE_T *)pb)[0] = LOWORD(dw);

    return ((LPBYTE)&((WORD HUGE_T *)pb)[1]);
} // pcmWriteSample_M16()

LPBYTE FNLOCAL pcmWriteSample_S16
(
    LPBYTE              pb,
    DWORD               dw
)
{
    ((DWORD HUGE_T *)pb)[0] = dw;

    return ((LPBYTE)&((DWORD HUGE_T *)pb)[1]);
} // pcmWriteSample_S16()



//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//  the following table is indexed by the wave format flags
//
//      x x x x
//      | | | |
//      | | | +------------ output is 1=stereo, 0=mono
//      | | +-------------- output is 1=16 bit, 0=8bit
//      | +---------------- input  is 1=stereo, 0=mono
//      +------------------ input  is 1=16 bit, 0=8bit
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

typedef LPBYTE (FNLOCAL *PCMREADSAMPLE)(LPBYTE pb, LPDWORD pdw);

static PCMREADSAMPLE pcmReadSample_Table[] =
{
    pcmReadSample_M08M08,
    pcmReadSample_S08M08,
    pcmReadSample_M16M08,
    pcmReadSample_S16M08,

    pcmReadSample_M08S08,
    pcmReadSample_S08S08,
    pcmReadSample_M16S08,
    pcmReadSample_S16S08,

    pcmReadSample_M08M16,
    pcmReadSample_S08M16,
    pcmReadSample_M16M16,
    pcmReadSample_S16M16,

    pcmReadSample_M08S16,
    pcmReadSample_S08S16,
    pcmReadSample_M16S16,
    pcmReadSample_S16S16,
};


//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;
//  the following table is indexed by the wave format flags
//
//      x x
//      | |
//      | +------------ output is 1=stereo, 0=mono
//      +-------------- output is 1=16 bit, 0=8bit
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;

typedef LPBYTE (FNLOCAL *PCMWRITESAMPLE)(LPBYTE pb, DWORD dw);

static PCMWRITESAMPLE pcmWriteSample_Table[] =
{
    pcmWriteSample_M08,
    pcmWriteSample_S08,
    pcmWriteSample_M16,
    pcmWriteSample_S16,
};



//--------------------------------------------------------------------------;
//
//  DWORD pcmConvert_C
//
//  Description:
//
//      The wave data must be PCM format with the following:
//          nSamplesPerSecond   :   1 - 0FFFFFFFFh
//          wBitsPerSample      :   8 or 16
//          nChannels           :   1 or 2
//
//  Arguments:
//      LPPCMWAVEFORMAT pwfSrc: Source PCM format.
//
//      LPBYTE pbSrc: Pointer to source bytes to convert.
//
//      LPPCMWAVEFORMAT pwfDst: Destination PCM format.
//
//      LPBYTE pbDst: Pointer to destination buffer.
//
//      DWORD dwSrcSamples: Source number of samples to convert.
//
//  Return (DWORD):
//      The return value is the total number of converted BYTES that were
//      placed in the destination buffer (pbDst).
//
//--------------------------------------------------------------------------;

#define PCM_WF_STEREO       0x0001
#define PCM_WF_16BIT        0x0002

EXTERN_C DWORD pcmConvert_C
(
    LPPCMWAVEFORMAT     pwfSrc,
    LPBYTE              pbSrc,
    LPPCMWAVEFORMAT     pwfDst,
    LPBYTE              pbDst,
    DWORD               dwSrcSamples,
    BOOL                fPartialSampleAtTheEnd,
    LPBYTE              pbDstEnd
)
{
    DWORD           dwSpsSrc;           // samples per second
    DWORD           dwSpsDst;           //
    LONG            lCurSample;
    LONG            lDecSample;
    UINT            wfSrc;              // wave format flags
    UINT            wfDst;              //
    PCMREADSAMPLE   fnReadSample;       // function to read a sample
    PCMWRITESAMPLE  fnWriteSample;      // function to write a sample
    DWORD           dwSample;
    LPBYTE          pbDstStart;


    //
    //  check for an easy out...
    //
    if (0L == dwSrcSamples)
        return (0L);

    //
    //  initialize a couple of things...
    //
    dwSpsSrc = pwfSrc->wf.nSamplesPerSec;
    dwSpsDst = pwfDst->wf.nSamplesPerSec;

    wfDst = (pwfDst->wf.nChannels >> 1);
    if (16 == pwfDst->wBitsPerSample)
        wfDst |= PCM_WF_16BIT;

    fnWriteSample = pcmWriteSample_Table[wfDst];

    wfSrc = (pwfSrc->wf.nChannels >> 1);
    if (16 == pwfSrc->wBitsPerSample)
        wfSrc |= PCM_WF_16BIT;

    fnReadSample = pcmReadSample_Table[(wfSrc << 2) | wfDst];


    //
    //
    //
    if( fPartialSampleAtTheEnd ) {
        //
        //  We'll convert the partial one individually.
        //
        dwSrcSamples--;
    }


    //
    //
    //
    pbDstStart   = pbDst;


    //
    //  all set to convert the wave data, either do a major or minor DDA
    //
    //      if (dwSpsSrc < dwSpsDst) --> DDA Major
    //      if (dwSpsSrc > dwSpsDst) --> DDA Minor
    //
    if (dwSpsSrc <= dwSpsDst)
    {
        //
        //  DDA major (dwSpsSrc < dwSpsDst)
        //
        //      start at dwSpsDst / 2
        //      decrement by dwSpsSrc
        //
        lCurSample = (dwSpsDst >> 1);
        lDecSample = dwSpsSrc;

        while (dwSrcSamples--)
        {
            pbSrc = fnReadSample(pbSrc, &dwSample);

            do
            {
                pbDst = fnWriteSample(pbDst, dwSample);
                lCurSample -= lDecSample;
            } while (lCurSample >= 0);

            lCurSample += dwSpsDst;
        }
    }
    else
    {
        //
        //  DDA minor (dwSpsSrc > dwSpsDst)
        //
        //      start at dwSpsSrc / 2
        //      decrement by dwSpsDst
        //
        lCurSample = (dwSpsSrc >> 1);
        lDecSample = dwSpsDst;

        while (dwSrcSamples--)
        {
            pbSrc = fnReadSample(pbSrc, &dwSample);

            lCurSample -= lDecSample;
            if (lCurSample >= 0)
                continue;

            pbDst = fnWriteSample(pbDst, dwSample);
            lCurSample += dwSpsSrc;
        }

    }


    //
    //
    //
    if( fPartialSampleAtTheEnd )
    {
        //
        //  Convert the partial sample.
        //
        pbSrc = fnReadSample( pbSrc, &dwSample );

        while( pbDst < pbDstEnd ) {
            pbDst = fnWriteSample( pbDst, dwSample );
        }

        ASSERT( pbDst == pbDstEnd );
    }


    //
    //
    //
    return ((DWORD)((BYTE HUGE *)pbDst - (BYTE HUGE *)pbDstStart));
} // pcmConvert_C()

