/*
*	Blade DLL Interface for LAME.
*
*	Copyright (c) 1999 - 2002 A.L. Faber
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA  02111-1307, USA.
*/

#include <windows.h>
#include <Windef.h>
#include "BladeMP3EncDLL.h"
#include <assert.h>
#include <stdio.h>

#include <lame/lame.h>

#ifdef	__cplusplus
extern "C" {
#endif

#define         Min(A, B)       ((A) < (B) ? (A) : (B))
#define         Max(A, B)       ((A) > (B) ? (A) : (B))

#define _RELEASEDEBUG 0

    // lame_enc DLL version number
    const BYTE MAJORVERSION = 1;
    const BYTE MINORVERSION = 32;


    // Local variables
    static DWORD				dwSampleBufferSize = 0;
    static HMODULE				gs_hModule = NULL;
    static BOOL					gs_bLogFile = FALSE;
    static lame_global_flags* gfp_save = NULL;

    // Local function prototypes
    static void dump_config(lame_global_flags* gfp);
    static void DebugPrintf(const char* pzFormat, ...);
    static void DispErr(wchar_t const* strErr);
    static void PresetOptions(lame_global_flags* gfp, LONG myPreset);


    static void DebugPrintf(const char* pzFormat, ...)
    {
        char	szBuffer[1024] = { '\0', };
        char	szFileName[MAX_PATH + 1] = { '\0', };
        va_list ap;

        // Get the full module (DLL) file name
        GetModuleFileNameA(gs_hModule,
            szFileName,
            sizeof(szFileName));

        // change file name extention
        szFileName[strlen(szFileName) - 3] = 't';
        szFileName[strlen(szFileName) - 2] = 'x';
        szFileName[strlen(szFileName) - 1] = 't';

        // start at beginning of the list
        va_start(ap, pzFormat);

        // copy it to the string buffer
        _vsnprintf(szBuffer, sizeof(szBuffer), pzFormat, ap);

        // log it to the file?
        if (gs_bLogFile)
        {
            FILE* fp = NULL;

            // try to open the log file
            fp = fopen(szFileName, "a+");

            // check file open result
            if (fp)
            {
                // write string to the file
                fputs(szBuffer, fp);

                // close the file
                fclose(fp);
            }
        }

#if defined _DEBUG || defined _RELEASEDEBUG
        OutputDebugStringA(szBuffer);
#endif

        va_end(ap);
    }


    static void PresetOptions(lame_global_flags* gfp, LONG myPreset)
    {
        switch (myPreset)
        {
        /*-1*/case LQP_NOPRESET:
            break;

        /*0*/case LQP_NORMAL_QUALITY:
            /*	lame_set_quality( gfp, 5 );*/
            break;

        /*1*/case LQP_LOW_QUALITY:
            lame_set_quality(gfp, 9);
            break;

        /*2*/case LQP_HIGH_QUALITY:
            lame_set_quality(gfp, 2);
            break;

        /*3*/case LQP_VOICE_QUALITY:				// --voice flag for experimental voice mode
            lame_set_mode(gfp, MONO);
            lame_set_preset(gfp, 56);
            break;

        /*4*/case LQP_R3MIX:					// --R3MIX
            lame_set_preset(gfp, R3MIX);
            break;

        /*5*/case LQP_VERYHIGH_QUALITY:
            lame_set_quality(gfp, 0);
            break;

        /*6*/case LQP_STANDARD:				// --PRESET STANDARD
            lame_set_preset(gfp, STANDARD);
            break;

        /*7*/case LQP_FAST_STANDARD:				// --PRESET FAST STANDARD
            lame_set_preset(gfp, STANDARD_FAST);
            break;

        /*8*/case LQP_EXTREME:				// --PRESET EXTREME
            lame_set_preset(gfp, EXTREME);
            break;

        /*9*/case LQP_FAST_EXTREME:				// --PRESET FAST EXTREME:
            lame_set_preset(gfp, EXTREME_FAST);
            break;

        /*10*/case LQP_INSANE:				// --PRESET INSANE
            lame_set_preset(gfp, INSANE);
            break;

        /*11*/case LQP_ABR:					// --PRESET ABR
            // handled in beInitStream
            break;

        /*12*/case LQP_CBR:					// --PRESET CBR
            // handled in beInitStream
            break;

        /*13*/case LQP_MEDIUM:					// --PRESET MEDIUM
            lame_set_preset(gfp, MEDIUM);
            break;

        /*14*/case LQP_FAST_MEDIUM:					// --PRESET FAST MEDIUM
            lame_set_preset(gfp, MEDIUM_FAST);
            break;

        /*1000*/case LQP_PHONE:
            lame_set_mode(gfp, MONO);
            lame_set_preset(gfp, 16);
            break;

        /*2000*/case LQP_SW:
            lame_set_mode(gfp, MONO);
            lame_set_preset(gfp, 24);
            break;

        /*3000*/case LQP_AM:
            lame_set_mode(gfp, MONO);
            lame_set_preset(gfp, 40);
            break;

        /*4000*/case LQP_FM:
            lame_set_preset(gfp, 112);
            break;

        /*5000*/case LQP_VOICE:
            lame_set_mode(gfp, MONO);
            lame_set_preset(gfp, 56);
            break;

        /*6000*/case LQP_RADIO:
            lame_set_preset(gfp, 112);
            break;

        /*7000*/case LQP_TAPE:
            lame_set_preset(gfp, 112);
            break;

        /*8000*/case LQP_HIFI:
            lame_set_preset(gfp, 160);
            break;

        /*9000*/case LQP_CD:
            lame_set_preset(gfp, 192);
            break;

        /*10000*/case LQP_STUDIO:
            lame_set_preset(gfp, 256);
            break;

        }
    }


    __declspec(dllexport) BE_ERR	beInitStream(PBE_CONFIG pbeConfig, PDWORD dwSamples, PDWORD dwBufferSize, PHBE_STREAM phbeStream)
    {
        int actual_bitrate;
        //2001-12-18
        BE_CONFIG			lameConfig = { 0, };
        int					nInitReturn = 0;
        lame_global_flags* gfp = NULL;

        // Init the global flags structure
        gfp = lame_init();
        *phbeStream = (HBE_STREAM)gfp;

        // clear out structure
        memset(&lameConfig, 0x00, CURRENT_STRUCT_SIZE);

        // Check if this is a regular BLADE_ENCODER header
        if (pbeConfig->dwConfig != BE_CONFIG_LAME)
        {
            int nCRC = pbeConfig->format.mp3.bCRC;
            int nVBR = (nCRC >> 12) & 0x0F;

            // Copy parameter from old Blade structure
            lameConfig.format.LHV1.dwSampleRate = pbeConfig->format.mp3.dwSampleRate;
            //for low bitrates, LAME will automatically downsample for better
            //sound quality.  Forcing output samplerate = input samplerate is not a good idea 
            //unless the user specifically requests it:
            //lameConfig.format.LHV1.dwReSampleRate=pbeConfig->format.mp3.dwSampleRate;
            lameConfig.format.LHV1.nMode = (pbeConfig->format.mp3.byMode & 0x0F);
            lameConfig.format.LHV1.dwBitrate = pbeConfig->format.mp3.wBitrate;
            lameConfig.format.LHV1.bPrivate = pbeConfig->format.mp3.bPrivate;
            lameConfig.format.LHV1.bOriginal = pbeConfig->format.mp3.bOriginal;
            lameConfig.format.LHV1.bCRC = nCRC & 0x01;
            lameConfig.format.LHV1.bCopyright = pbeConfig->format.mp3.bCopyright;

            // Fill out the unknowns
            lameConfig.format.LHV1.dwStructSize = CURRENT_STRUCT_SIZE;
            lameConfig.format.LHV1.dwStructVersion = CURRENT_STRUCT_VERSION;

            // Get VBR setting from fourth nibble
            if (nVBR > 0)
            {
                lameConfig.format.LHV1.bWriteVBRHeader = TRUE;
                lameConfig.format.LHV1.bEnableVBR = TRUE;
                lameConfig.format.LHV1.nVBRQuality = nVBR - 1;
            }

            // Get Quality from third nibble
            lameConfig.format.LHV1.nPreset = ((nCRC >> 8) & 0x0F);

        }
        else
        {
            // Copy the parameters
            memcpy(&lameConfig, pbeConfig, pbeConfig->format.LHV1.dwStructSize);
        }

        // --------------- Set arguments to LAME encoder -------------------------

        // Set input sample frequency
        lame_set_in_samplerate(gfp, lameConfig.format.LHV1.dwSampleRate);

        // disable INFO/VBR tag by default.  
        // if this tag is used, the calling program must call beWriteVBRTag()
        // after encoding.  But the original DLL documentation does not 
        // require the 
        // app to call beWriteVBRTag() unless they have specifically
        // set LHV1.bWriteVBRHeader=TRUE.  Thus the default setting should
        // be disabled.  
        lame_set_bWriteVbrTag(gfp, 0);

        //2001-12-18 Dibrom's ABR preset stuff

        if (lameConfig.format.LHV1.nPreset == LQP_ABR)		// --ALT-PRESET ABR
        {
            actual_bitrate = lameConfig.format.LHV1.dwVbrAbr_bps / 1000;

            // limit range
            if (actual_bitrate > 320)
            {
                actual_bitrate = 320;
            }

            if (actual_bitrate < 8)
            {
                actual_bitrate = 8;
            }

            lame_set_preset(gfp, actual_bitrate);
        }

        // end Dibrom's ABR preset 2001-12-18 ****** START OF CBR

        if (lameConfig.format.LHV1.nPreset == LQP_CBR)		// --ALT-PRESET CBR
        {
            actual_bitrate = lameConfig.format.LHV1.dwBitrate;
            lame_set_preset(gfp, actual_bitrate);
            lame_set_VBR(gfp, vbr_off);
        }

        // end Dibrom's CBR preset 2001-12-18

        // The following settings only used when preset is not one of the LAME QUALITY Presets
        if ((int)lameConfig.format.LHV1.nPreset < (int)LQP_STANDARD)
        {
            switch (lameConfig.format.LHV1.nMode)
            {
            case BE_MP3_MODE_STEREO:
                lame_set_mode(gfp, STEREO);
                lame_set_num_channels(gfp, 2);
                break;
            case BE_MP3_MODE_JSTEREO:
                lame_set_mode(gfp, JOINT_STEREO);
                //lame_set_force_ms( gfp, bForceMS ); // no check box to force this?
                lame_set_num_channels(gfp, 2);
                break;
            case BE_MP3_MODE_MONO:
                lame_set_mode(gfp, MONO);
                lame_set_num_channels(gfp, 1);
                break;
            case BE_MP3_MODE_DUALCHANNEL:
                lame_set_mode(gfp, DUAL_CHANNEL);
                lame_set_num_channels(gfp, 2);
                break;
            default:
            {
                DebugPrintf("Invalid lameConfig.format.LHV1.nMode, value is %d\n", lameConfig.format.LHV1.nMode);
                return BE_ERR_INVALID_FORMAT_PARAMETERS;
            }
            }

            if (lameConfig.format.LHV1.bEnableVBR)
            {
                /* set VBR quality */
                lame_set_VBR_q(gfp, lameConfig.format.LHV1.nVBRQuality);

                /* select proper VBR method */
                switch (lameConfig.format.LHV1.nVbrMethod)
                {
                case VBR_METHOD_NONE:
                    lame_set_VBR(gfp, vbr_off);
                    break;

                case VBR_METHOD_DEFAULT:
                    lame_set_VBR(gfp, vbr_default);
                    break;

                case VBR_METHOD_OLD:
                    lame_set_VBR(gfp, vbr_rh);
                    break;

                case VBR_METHOD_MTRH:
                case VBR_METHOD_NEW:
                    /*
                    * the --vbr-mtrh commandline switch is obsolete.
                    * now --vbr-mtrh is known as --vbr-new
                    */
                    lame_set_VBR(gfp, vbr_mtrh);
                    break;

                case VBR_METHOD_ABR:
                    lame_set_VBR(gfp, vbr_abr);
                    break;

                default:
                    /* unsupported VBR method */
                    assert(FALSE);
                }
            }
            else
            {
                /* use CBR encoding method, so turn off VBR */
                lame_set_VBR(gfp, vbr_off);
            }

            /* Set bitrate.  (CDex users always specify bitrate=Min bitrate when using VBR) */
            lame_set_brate(gfp, lameConfig.format.LHV1.dwBitrate);

            /* check if we have to use ABR, in order to backwards compatible, this
            * condition should still be checked indepedent of the nVbrMethod method
            */
            if (lameConfig.format.LHV1.dwVbrAbr_bps > 0)
            {
                /* set VBR method to ABR */
                lame_set_VBR(gfp, vbr_abr);

                /* calculate to kbps, round to nearest kbps */
                lame_set_VBR_mean_bitrate_kbps(gfp, (lameConfig.format.LHV1.dwVbrAbr_bps + 500) / 1000);

                /* limit range */
                if (lame_get_VBR_mean_bitrate_kbps(gfp) > 320)
                {
                    lame_set_VBR_mean_bitrate_kbps(gfp, 320);
                }

                if (lame_get_VBR_mean_bitrate_kbps(gfp) < 8)
                {
                    lame_set_VBR_mean_bitrate_kbps(gfp, 8);
                }
            }

        }

        // First set all the preset options
        if (LQP_NOPRESET != lameConfig.format.LHV1.nPreset)
        {
            PresetOptions(gfp, lameConfig.format.LHV1.nPreset);
        }


        // Set frequency resampling rate, if specified
        if (lameConfig.format.LHV1.dwReSampleRate > 0)
        {
            lame_set_out_samplerate(gfp, lameConfig.format.LHV1.dwReSampleRate);
        }


        switch (lameConfig.format.LHV1.nMode)
        {
        case BE_MP3_MODE_MONO:
            lame_set_mode(gfp, MONO);
            lame_set_num_channels(gfp, 1);
            break;

        default:
            break;
        }


        // Use strict ISO encoding?
        lame_set_strict_ISO(gfp, (lameConfig.format.LHV1.bStrictIso) ? 1 : 0);

        // Set copyright flag?
        if (lameConfig.format.LHV1.bCopyright)
        {
            lame_set_copyright(gfp, 1);
        }

        // Do we have to tag  it as non original 
        if (!lameConfig.format.LHV1.bOriginal)
        {
            lame_set_original(gfp, 0);
        }
        else
        {
            lame_set_original(gfp, 1);
        }

        // Add CRC?
        if (lameConfig.format.LHV1.bCRC)
        {
            lame_set_error_protection(gfp, 1);
        }
        else
        {
            lame_set_error_protection(gfp, 0);
        }

        // Set private bit?
        if (lameConfig.format.LHV1.bPrivate)
        {
            lame_set_extension(gfp, 1);
        }
        else
        {
            lame_set_extension(gfp, 0);
        }


        // Set VBR min bitrate, if specified
        if (lameConfig.format.LHV1.dwBitrate > 0)
        {
            lame_set_VBR_min_bitrate_kbps(gfp, lameConfig.format.LHV1.dwBitrate);
        }

        // Set Maxbitrate, if specified
        if (lameConfig.format.LHV1.dwMaxBitrate > 0)
        {
            lame_set_VBR_max_bitrate_kbps(gfp, lameConfig.format.LHV1.dwMaxBitrate);
        }
        // Set bit resovoir option
        if (lameConfig.format.LHV1.bNoRes)
        {
            lame_set_disable_reservoir(gfp, 1);
        }

        // check if the VBR tag is required
        if (lameConfig.format.LHV1.bWriteVBRHeader)
        {
            lame_set_bWriteVbrTag(gfp, 1);
        }
        else
        {
            lame_set_bWriteVbrTag(gfp, 0);
        }

        // Override Quality setting, use HIGHBYTE = NOT LOWBYTE to be backwards compatible
        if ((lameConfig.format.LHV1.nQuality & 0xFF) ==
            ((~(lameConfig.format.LHV1.nQuality >> 8)) & 0xFF))
        {
            lame_set_quality(gfp, lameConfig.format.LHV1.nQuality & 0xFF);
        }

        if (0 != (nInitReturn = lame_init_params(gfp)))
        {
            return nInitReturn;
        }

        //LAME encoding call will accept any number of samples.  
        if (0 == lame_get_version(gfp))
        {
            // For MPEG-II, only 576 samples per frame per channel
            *dwSamples = 576 * lame_get_num_channels(gfp);
        }
        else
        {
            // For MPEG-I, 1152 samples per frame per channel
            *dwSamples = 1152 * lame_get_num_channels(gfp);
        }

        // Set the input sample buffer size, so we know what we can expect
        dwSampleBufferSize = *dwSamples;

        // Set MP3 buffer size, conservative estimate
        *dwBufferSize = (DWORD)(1.25 * (*dwSamples / lame_get_num_channels(gfp)) + 7200);

        // For debugging purposes
        dump_config(gfp);

        // Everything went OK, thus return SUCCESSFUL
        return BE_ERR_SUCCESSFUL;
    }



    __declspec(dllexport) BE_ERR	beFlushNoGap(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput)
    {
        int nOutputSamples = 0;

        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        // Init the global flags structure
        nOutputSamples = lame_encode_flush_nogap(gfp, pOutput, LAME_MAXMP3BUFFER);

        if (nOutputSamples < 0)
        {
            *pdwOutput = 0;
            return BE_ERR_BUFFER_TOO_SMALL;
        }
        else
        {
            *pdwOutput = nOutputSamples;
        }

        return BE_ERR_SUCCESSFUL;
    }

    __declspec(dllexport) BE_ERR	beDeinitStream(HBE_STREAM hbeStream, PBYTE pOutput, PDWORD pdwOutput)
    {
        int nOutputSamples = 0;

        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        nOutputSamples = lame_encode_flush(gfp, pOutput, 0);

        if (nOutputSamples < 0)
        {
            *pdwOutput = 0;
            return BE_ERR_BUFFER_TOO_SMALL;
        }
        else
        {
            *pdwOutput = nOutputSamples;
        }

        return BE_ERR_SUCCESSFUL;
    }


    __declspec(dllexport) BE_ERR	beCloseStream(HBE_STREAM hbeStream)
    {
        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        // lame will be close in VbrWriteTag function
        if (!lame_get_bWriteVbrTag(gfp))
        {
            // clean up of allocated memory
            lame_close(gfp);

            gfp_save = NULL;
        }
        else
        {
            gfp_save = (lame_global_flags*)hbeStream;
        }

        // DeInit encoder
        return BE_ERR_SUCCESSFUL;
    }



    __declspec(dllexport) VOID		beVersion(PBE_VERSION pbeVersion)
    {
        // DLL Release date
        char lpszDate[20] = { '\0', };
        char lpszTemp[5] = { '\0', };
        lame_version_t lv = { 0, };


        // Set DLL interface version
        pbeVersion->byDLLMajorVersion = MAJORVERSION;
        pbeVersion->byDLLMinorVersion = MINORVERSION;

        get_lame_version_numerical(&lv);

        // Set Engine version number (Same as Lame version)
        pbeVersion->byMajorVersion = (BYTE)lv.major;
        pbeVersion->byMinorVersion = (BYTE)lv.minor;
        pbeVersion->byAlphaLevel = (BYTE)lv.alpha;
        pbeVersion->byBetaLevel = (BYTE)lv.beta;

#ifdef MMX_choose_table
        pbeVersion->byMMXEnabled = 1;
#else
        pbeVersion->byMMXEnabled = 0;
#endif

        memset(pbeVersion->btReserved, 0, sizeof(pbeVersion->btReserved));

        // Get compilation date
        strcpy(lpszDate, __DATE__);

        // Get the first three character, which is the month
        strncpy(lpszTemp, lpszDate, 3);
        lpszTemp[3] = '\0';
        pbeVersion->byMonth = 1;

        // Set month
        if (strcmp(lpszTemp, "Jan") == 0)	pbeVersion->byMonth = 1;
        if (strcmp(lpszTemp, "Feb") == 0)	pbeVersion->byMonth = 2;
        if (strcmp(lpszTemp, "Mar") == 0)	pbeVersion->byMonth = 3;
        if (strcmp(lpszTemp, "Apr") == 0)	pbeVersion->byMonth = 4;
        if (strcmp(lpszTemp, "May") == 0)	pbeVersion->byMonth = 5;
        if (strcmp(lpszTemp, "Jun") == 0)	pbeVersion->byMonth = 6;
        if (strcmp(lpszTemp, "Jul") == 0)	pbeVersion->byMonth = 7;
        if (strcmp(lpszTemp, "Aug") == 0)	pbeVersion->byMonth = 8;
        if (strcmp(lpszTemp, "Sep") == 0)	pbeVersion->byMonth = 9;
        if (strcmp(lpszTemp, "Oct") == 0)	pbeVersion->byMonth = 10;
        if (strcmp(lpszTemp, "Nov") == 0)	pbeVersion->byMonth = 11;
        if (strcmp(lpszTemp, "Dec") == 0)	pbeVersion->byMonth = 12;

        // Get day of month string (char [4..5])
        pbeVersion->byDay = (BYTE)atoi(lpszDate + 4);

        // Get year of compilation date (char [7..10])
        pbeVersion->wYear = (WORD)atoi(lpszDate + 7);

        memset(pbeVersion->zHomepage, 0x00, BE_MAX_HOMEPAGE);

        strcpy(pbeVersion->zHomepage, "http://www.mp3dev.org/");
    }

    __declspec(dllexport) BE_ERR	beEncodeChunk(HBE_STREAM hbeStream, DWORD nSamples,
        PSHORT pSamples, PBYTE pOutput, PDWORD pdwOutput)
    {
        // Encode it
        int dwSamples;
        int	nOutputSamples = 0;
        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        dwSamples = nSamples / lame_get_num_channels(gfp);

        // old versions of lame_enc.dll required exactly 1152 samples
        // and worked even if nSamples accidently set to 2304 
        // simulate this behavoir:
        if (1 == lame_get_num_channels(gfp) && nSamples == 2304)
        {
            dwSamples /= 2;
        }


        if (1 == lame_get_num_channels(gfp))
        {
            nOutputSamples = lame_encode_buffer(gfp, pSamples, pSamples, dwSamples, pOutput, 0);
        }
        else
        {
            nOutputSamples = lame_encode_buffer_interleaved(gfp, pSamples, dwSamples, pOutput, 0);
        }


        if (nOutputSamples < 0)
        {
            *pdwOutput = 0;
            return BE_ERR_BUFFER_TOO_SMALL;
        }
        else
        {
            *pdwOutput = (DWORD)nOutputSamples;
        }

        return BE_ERR_SUCCESSFUL;
    }


    // accept floating point audio samples, scaled to the range of a signed 16-bit
    //  integer (within +/- 32768), in non-interleaved channels  -- DSPguru, jd
    __declspec(dllexport) BE_ERR	beEncodeChunkFloatS16NI(HBE_STREAM hbeStream, DWORD nSamples,
        PFLOAT buffer_l, PFLOAT buffer_r, PBYTE pOutput, PDWORD pdwOutput)
    {
        int nOutputSamples;
        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        nOutputSamples = lame_encode_buffer_float(gfp, buffer_l, buffer_r, nSamples, pOutput, 0);

        if (nOutputSamples >= 0)
        {
            *pdwOutput = (DWORD)nOutputSamples;
        }
        else
        {
            *pdwOutput = 0;
            return BE_ERR_BUFFER_TOO_SMALL;
        }

        return BE_ERR_SUCCESSFUL;
    }

    static int
        maybeSyncWord(FILE* fpStream)
    {
        unsigned char mp3_frame_header[4];
        size_t nbytes = fread(mp3_frame_header, 1, sizeof(mp3_frame_header), fpStream);
        if (nbytes != sizeof(mp3_frame_header)) {
            return -1;
        }
        if (mp3_frame_header[0] != 0xffu) {
            return -1; /* doesn't look like a sync word */
        }
        if ((mp3_frame_header[1] & 0xE0u) != 0xE0u) {
            return -1; /* doesn't look like a sync word */
        }
        return 0;
    }

    static int
        skipId3v2(FILE* fpStream, size_t lametag_frame_size)
    {
        size_t  nbytes;
        size_t  id3v2TagSize = 0;
        unsigned char id3v2Header[10];

        /* seek to the beginning of the stream */
        if (fseek(fpStream, 0, SEEK_SET) != 0) {
            return -2;  /* not seekable, abort */
        }
        /* read 10 bytes in case there's an ID3 version 2 header here */
        nbytes = fread(id3v2Header, 1, sizeof(id3v2Header), fpStream);
        if (nbytes != sizeof(id3v2Header)) {
            return -3;  /* not readable, maybe opened Write-Only */
        }
        /* does the stream begin with the ID3 version 2 file identifier? */
        if (!strncmp((char*)id3v2Header, "ID3", 3)) {
            /* the tag size (minus the 10-byte header) is encoded into four
            * bytes where the most significant bit is clear in each byte
            */
            id3v2TagSize = (((id3v2Header[6] & 0x7f) << 21)
                | ((id3v2Header[7] & 0x7f) << 14)
                | ((id3v2Header[8] & 0x7f) << 7)
                | (id3v2Header[9] & 0x7f))
                + sizeof id3v2Header;
        }
        /* Seek to the beginning of the audio stream */
        if (fseek(fpStream, id3v2TagSize, SEEK_SET) != 0) {
            return -2;
        }
        if (maybeSyncWord(fpStream) != 0) {
            return -1;
        }
        if (fseek(fpStream, id3v2TagSize + lametag_frame_size, SEEK_SET) != 0) {
            return -2;
        }
        if (maybeSyncWord(fpStream) != 0) {
            return -1;
        }
        /* OK, it seems we found our LAME-Tag/Xing frame again */
        /* Seek to the beginning of the audio stream */
        if (fseek(fpStream, id3v2TagSize, SEEK_SET) != 0) {
            return -2;
        }
        return 0;
    }

    static BE_ERR
        updateLameTagFrame(lame_global_flags* gfp, FILE* fpStream)
    {
        size_t n = lame_get_lametag_frame(gfp, 0, 0); /* ask for bufer size */

        if (n > 0)
        {
            unsigned char* buffer = 0;
            size_t m = 1;

            if (0 != skipId3v2(fpStream, n))
            {
                DispErr("Error updating LAME-tag frame:\n\n"
                    "can't locate old frame\n");
                return BE_ERR_INVALID_FORMAT_PARAMETERS;
            }

            buffer = (unsigned char*)malloc(n);

            if (buffer == 0)
            {
                DispErr("Error updating LAME-tag frame:\n\n"
                    "can't allocate frame buffer\n");
                return BE_ERR_INVALID_FORMAT_PARAMETERS;
            }

            /* Put it all to disk again */
            n = lame_get_lametag_frame(gfp, buffer, n);
            if (n > 0)
            {
                m = fwrite(buffer, n, 1, fpStream);
            }
            free(buffer);

            if (m != 1)
            {
                DispErr("Error updating LAME-tag frame:\n\n"
                    "couldn't write frame into file\n");
                return BE_ERR_INVALID_FORMAT_PARAMETERS;
            }
        }
        return BE_ERR_SUCCESSFUL;
    }

    __declspec(dllexport) BE_ERR beWriteInfoTag(HBE_STREAM hbeStream,
        LPCSTR lpszFileName)
    {
        FILE* fpStream = NULL;
        BE_ERR beResult = BE_ERR_SUCCESSFUL;

        lame_global_flags* gfp = (lame_global_flags*)hbeStream;

        if (NULL != gfp)
        {
            // Do we have to write the VBR tag?
            if (lame_get_bWriteVbrTag(gfp))
            {
                // Try to open the file
                fpStream = fopen(lpszFileName, "rb+");

                // Check file open result
                if (NULL == fpStream)
                {
                    beResult = BE_ERR_INVALID_FORMAT_PARAMETERS;
                    DispErr("Error updating LAME-tag frame:\n\n"
                        "can't open file for reading and writing\n");
                }
                else
                {
                    beResult = updateLameTagFrame(gfp, fpStream);

                    // Close the file stream
                    fclose(fpStream);
                }
            }

            // clean up of allocated memory
            lame_close(gfp);
        }
        else
        {
            beResult = BE_ERR_INVALID_FORMAT_PARAMETERS;
        }

        // return result
        return beResult;
    }

    // for backwards compatiblity
    __declspec(dllexport) BE_ERR beWriteVBRHeader(LPCSTR lpszFileName)
    {
        return beWriteInfoTag((HBE_STREAM)gfp_save, lpszFileName);
    }


    BOOL APIENTRY DllMain(HANDLE hModule,
        DWORD  ul_reason_for_call,
        LPVOID lpReserved)
    {
        (void)lpReserved;
        gs_hModule = (HMODULE)hModule;

        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
            // Enable debug/logging?
            gs_bLogFile = GetPrivateProfileIntA("Debug", "WriteLogFile", gs_bLogFile, "lame_enc.ini");
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }


    static void dump_config(lame_global_flags* gfp)
    {
        DebugPrintf("\n\nLame_enc configuration options:\n");
        DebugPrintf("==========================================================\n");

        DebugPrintf("version                =%d\n", lame_get_version(gfp));
        DebugPrintf("Layer                  =3\n");
        DebugPrintf("mode                   =");
        switch (lame_get_mode(gfp))
        {
        case STEREO:       DebugPrintf("Stereo\n"); break;
        case JOINT_STEREO: DebugPrintf("Joint-Stereo\n"); break;
        case DUAL_CHANNEL: DebugPrintf("Forced Stereo\n"); break;
        case MONO:         DebugPrintf("Mono\n"); break;
        case NOT_SET:      /* FALLTROUGH */
        default:           DebugPrintf("Error (unknown)\n"); break;
        }

        DebugPrintf("Input sample rate      =%.1f kHz\n", lame_get_in_samplerate(gfp) / 1000.0);
        DebugPrintf("Output sample rate     =%.1f kHz\n", lame_get_out_samplerate(gfp) / 1000.0);

        DebugPrintf("bitrate                =%d kbps\n", lame_get_brate(gfp));
        DebugPrintf("Quality Setting        =%d\n", lame_get_quality(gfp));

        DebugPrintf("Low pass frequency     =%d\n", lame_get_lowpassfreq(gfp));
        DebugPrintf("Low pass width         =%d\n", lame_get_lowpasswidth(gfp));

        DebugPrintf("High pass frequency    =%d\n", lame_get_highpassfreq(gfp));
        DebugPrintf("High pass width        =%d\n", lame_get_highpasswidth(gfp));

        DebugPrintf("No short blocks        =%d\n", lame_get_no_short_blocks(gfp));
        DebugPrintf("Force short blocks     =%d\n", lame_get_force_short_blocks(gfp));

        DebugPrintf("de-emphasis            =%d\n", lame_get_emphasis(gfp));
        DebugPrintf("private flag           =%d\n", lame_get_extension(gfp));

        DebugPrintf("copyright flag         =%d\n", lame_get_copyright(gfp));
        DebugPrintf("original flag          =%d\n", lame_get_original(gfp));
        DebugPrintf("CRC                    =%s\n", lame_get_error_protection(gfp) ? "on" : "off");
        DebugPrintf("Fast mode              =%s\n", (lame_get_quality(gfp)) ? "enabled" : "disabled");
        DebugPrintf("Force mid/side stereo  =%s\n", (lame_get_force_ms(gfp)) ? "enabled" : "disabled");
        DebugPrintf("Disable Reservoir      =%d\n", lame_get_disable_reservoir(gfp));
        DebugPrintf("Allow diff-short       =%d\n", lame_get_allow_diff_short(gfp));
        DebugPrintf("Interchannel masking   =%f\n", lame_get_interChRatio(gfp));
        DebugPrintf("Strict ISO Encoding    =%s\n", (lame_get_strict_ISO(gfp)) ? "Yes" : "No");
        DebugPrintf("Scale                  =%5.2f\n", lame_get_scale(gfp));

        DebugPrintf("VBR                    =%s, VBR_q =%d, VBR method =",
            (lame_get_VBR(gfp) != vbr_off) ? "enabled" : "disabled",
            lame_get_VBR_q(gfp));

        switch (lame_get_VBR(gfp))
        {
        case vbr_off:	DebugPrintf("vbr_off\n");	break;
        case vbr_mt:	DebugPrintf("vbr_mt \n");	break;
        case vbr_rh:	DebugPrintf("vbr_rh \n");	break;
        case vbr_mtrh:	DebugPrintf("vbr_mtrh \n");	break;
        case vbr_abr:
            DebugPrintf("vbr_abr (average bitrate %d kbps)\n", lame_get_VBR_mean_bitrate_kbps(gfp));
            break;
        default:
            DebugPrintf("error, unknown VBR setting\n");
            break;
        }

        DebugPrintf("Vbr Min bitrate        =%d kbps\n", lame_get_VBR_min_bitrate_kbps(gfp));
        DebugPrintf("Vbr Max bitrate        =%d kbps\n", lame_get_VBR_max_bitrate_kbps(gfp));

        DebugPrintf("Write VBR Header       =%s\n", (lame_get_bWriteVbrTag(gfp)) ? "Yes" : "No");
        DebugPrintf("VBR Hard min           =%d\n", lame_get_VBR_hard_min(gfp));

        DebugPrintf("ATH Only               =%d\n", lame_get_ATHonly(gfp));
        DebugPrintf("ATH short              =%d\n", lame_get_ATHshort(gfp));
        DebugPrintf("ATH no                 =%d\n", lame_get_noATH(gfp));
        DebugPrintf("ATH type               =%d\n", lame_get_ATHtype(gfp));
        DebugPrintf("ATH lower              =%f\n", lame_get_ATHlower(gfp));
        DebugPrintf("ATH aa                 =%d\n", lame_get_athaa_type(gfp));
        //DebugPrintf("ATH aa  loudapprox     =%d\n", lame_get_athaa_loudapprox( gfp ) );
        DebugPrintf("ATH aa  sensitivity    =%f\n", lame_get_athaa_sensitivity(gfp));

        DebugPrintf("Experimental nspsytune =%d\n", lame_get_exp_nspsytune(gfp));
        DebugPrintf("Experimental X         =%d\n", lame_get_experimentalX(gfp));
        DebugPrintf("Experimental Y         =%d\n", lame_get_experimentalY(gfp));
        DebugPrintf("Experimental Z         =%d\n", lame_get_experimentalZ(gfp));
    }


    static void DispErr(wchar_t const* strErr)
    {
        MessageBox(NULL, strErr, L"LAME_ENC.DLL", MB_OK | MB_ICONHAND);
    }

#ifdef	__cplusplus
}
#endif