/'===============================================================================================
 OUTPUT_MP3.DLL
 Copyright (c), Firelight Technologies Pty, Ltd 2004-2011.

 Shows how to write an FMOD output plugin that writes the output to an mp3 file using 
 lame_enc.dll.

 Most of this source code that does the encoding is taken from the LAME encoder example.
 An FMOD Ex output plugin is created by declaring the FMODGetOutputDescription function,
 then filling out a FMOD_OUTPUT_DESCRIPTION and returning a pointer to it.
 FMOD will then call those functions when appropriate.

 To get the output from FMOD so that you can write it to your sound device (or LAME encoder
 function in this case), FMOD_OUTPUT_STATE::readfrommixer is called to run the mixer.

 We acknowledge that we are using LAME, which originates from www.mp3dev.org.
 LAME is under the LGPL and as an external FMOD plugin with full source code for the interface
 it is allowable under the LGPL to be distributed in this fashion.

==============================================================================================='/
' compile: fbc -dll output_mp3.bas

#include "windows.bi"
#include "crt.bi"

#include "fmodex/fmodex.bi"
#include "fmodex/fmod_output.bi"
#include "fmodex/fmod_errors.bi"

#include "BladeMP3EncDll.bi"

type outputmp3_state
    as FILE               ptr mFP    
	as HINSTANCE	        mDLL
	as HBE_STREAM	        hbeStream
	as PBYTE               pMP3Buffer
	as PSHORT              pWAVBuffer
	as BE_VERSION	        Version
    as DWORD		        dwMP3Buffer
    as DWORD               dwSamples
end type

dim shared as FMOD_OUTPUT_DESCRIPTION mp3output = any

declare function OutputMP3_GetNumDriversCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval numdrivers as integer ptr) as  FMOD_RESULT
declare function OutputMP3_GetDriverNameCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval id as integer, byval name_ as zstring ptr, byval namelen as integer) as FMOD_RESULT
declare function OutputMP3_InitCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval selecteddriver as integer, byval flags as FMOD_INITFLAGS, byval outputrate as integer ptr, byval outputchannels as integer, byval outputformat as FMOD_SOUND_FORMAT ptr, byval dspbufferlength as integer, byval dspnumbuffers as integer, byval hwnd as any ptr) as FMOD_RESULT
declare function OutputMP3_CloseCallback(byval output_ as FMOD_OUTPUT_STATE ptr) as FMOD_RESULT
declare function OutputMP3_UpdateCallback(byval output_ as FMOD_OUTPUT_STATE ptr) as FMOD_RESULT
declare function OutputMP3_GetHandleCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval handle as any ptr ptr) as FMOD_RESULT


/'
    FMODGetOutputDescription is mandantory for every fmod plugin.  This is the symbol the registerplugin function searches for.
    Must be declared with F_API to make it export as stdcall.
'/
function FMODGetOutputDescription stdcall alias "_FMODGetOutputDescription"() as FMOD_OUTPUT_DESCRIPTION ptr export
    clear(mp3output, 0, sizeof(FMOD_OUTPUT_DESCRIPTION))

    mp3output.name          = @"FMOD MP3 Output"
    mp3output.version       = &h00010000
    mp3output.polling       = 0                                    /' False = no thread is created.  Plugin must drive FMOD's mixer '/
    mp3output.getnumdrivers = @OutputMP3_GetNumDriversCallback
    mp3output.getdrivername = @OutputMP3_GetDriverNameCallback
    mp3output.getdrivercaps = 0
    mp3output.init          = @OutputMP3_InitCallback
    mp3output.close         = @OutputMP3_CloseCallback
    mp3output.update        = @OutputMP3_UpdateCallback
    mp3output.gethandle     = @OutputMP3_GetHandleCallback
    mp3output.getposition   = 0                                    /' Not a polling output so getposition is never called '/
    mp3output.lock          = 0                                    /' Not a polling output so lock is never called '/
    mp3output.unlock        = 0                                    /' Not a polling output so unlock is never called '/

    function = @mp3output
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_GetNumDriversCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval numdrivers as integer ptr) as  FMOD_RESULt
    *numdrivers = 1

    function = FMOD_OK
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_GetDriverNameCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval id as integer, byval name_ as zstring ptr, byval namelen as integer) as FMOD_RESULT
    strncpy(name_, "fmodoutput.mp3", namelen)

    function = FMOD_OK
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_InitCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval selecteddriver as integer, byval flags as FMOD_INITFLAGS, byval outputrate as integer ptr, byval outputchannels as integer, byval outputformat as FMOD_SOUND_FORMAT ptr, byval dspbufferlength as integer, byval dspnumbuffers as integer, byval hwnd as any ptr) as FMOD_RESULT
    dim as outputmp3_state ptr state
	dim as BE_CONFIG	     beConfig = type(0)
	dim as BE_ERR		     err_ = 0
    dim as zstring * 256            filename

    if (outputchannels > 2) then
        return FMOD_ERR_TOOMANYCHANNELS
    end if

    /'
        Create a structure that we can attach to the plugin instance.
    '/
    state = CAllocate(sizeof(outputmp3_state), 1)
    if (state = 0) then
        return FMOD_ERR_MEMORY
    end if
    output_->plugindata = state
    


    *outputformat = FMOD_SOUND_FORMAT_PCM16


	' Get the version number
	beVersion( @state->Version )

    clear(beConfig,0,sizeof(beConfig))					' clear all fields

	' use the LAME config structure
	beConfig.dwConfig = BE_CONFIG_LAME

	' this are the default settings for testcase.wav

    ' FMOD NOTE : The 'hwnd' param could be used to pass in info about the bitrate and encoding settings.

	beConfig.format.LHV1.dwStructVersion	= 1
	beConfig.format.LHV1.dwStructSize		= sizeof(beConfig)		
	beConfig.format.LHV1.dwSampleRate		= *outputrate           ' INPUT FREQUENCY
	beConfig.format.LHV1.dwReSampleRate		= 0					' DON"T RESAMPLE
	beConfig.format.LHV1.nMode				= iif(outputchannels = 1,BE_MP3_MODE_MONO ,BE_MP3_MODE_JSTEREO)	' OUTPUT IN STEREO
	beConfig.format.LHV1.dwBitrate			= 128					' MINIMUM BIT RATE
	beConfig.format.LHV1.nPreset			= LQP_R3MIX		    ' QUALITY PRESET SETTING
	beConfig.format.LHV1.dwMpegVersion		= MPEG1				' MPEG VERSION (I or II)
	beConfig.format.LHV1.dwPsyModel			= 0					' USE DEFAULT PSYCHOACOUSTIC MODEL 
	beConfig.format.LHV1.dwEmphasis			= 0					' NO EMPHASIS TURNED ON
	beConfig.format.LHV1.bOriginal			= TRUE					' SET ORIGINAL FLAG
	beConfig.format.LHV1.bWriteVBRHeader	= TRUE					' Write INFO tag

'	beConfig.format.LHV1.dwMaxBitrate		= 320					' MAXIMUM BIT RATE
'	beConfig.format.LHV1.bCRC				= TRUE					' INSERT CRC
'	beConfig.format.LHV1.bCopyright			= TRUE					' SET COPYRIGHT FLAG	
'	beConfig.format.LHV1.bPrivate			= TRUE					' SET PRIVATE FLAG
'	beConfig.format.LHV1.bWriteVBRHeader	= TRUE					' YES, WRITE THE XING VBR HEADER
'	beConfig.format.LHV1.bEnableVBR			= TRUE					' USE VBR
'	beConfig.format.LHV1.nVBRQuality		= 5					' SET VBR QUALITY
	beConfig.format.LHV1.bNoRes				= TRUE					' No Bit resorvoir

' Preset Test
'	beConfig.format.LHV1.nPreset			= LQP_PHONE

	' Init the MP3 Stream
	err_ = beInitStream(@beConfig, @state->dwSamples, @state->dwMP3Buffer, @state->hbeStream)

	' Check result
	if(err_ <> BE_ERR_SUCCESSFUL) then
		return FMOD_ERR_PLUGIN
	end if

	' Allocate MP3 buffer
	state->pMP3Buffer = allocate(state->dwMP3Buffer)
	if(state->pMP3Buffer = 0) then
        return FMOD_ERR_MEMORY
	end if

	' Allocate WAV buffer
	state->pWAVBuffer = allocate(state->dwSamples * sizeof(SHORT))
	if (state->pWAVBuffer = 0) then
        return FMOD_ERR_MEMORY
	end if

    if (hwnd = 0) then
        strncpy(@filename, "fmodoutput.mp3", 256)
    else
        strncpy(@filename, cast(zstring ptr,hwnd), 256)
    end if

    state->mFP = fopen(@filename, "wb")
    if (state->mFP = 0) then
        return FMOD_ERR_FILE_NOTFOUND
    end if

    function = FMOD_OK
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_CloseCallback(byval output_ as FMOD_OUTPUT_STATE ptr) as FMOD_RESULT
    dim as outputmp3_state ptr state = output_->plugindata
	dim as DWORD dwRead=0
	dim as DWORD dwWrite=0
	dim as DWORD dwDone=0
	dim as DWORD dwFileSize=0   
    dim as BE_ERR err_ = any

    if (state = 0) then
        return FMOD_OK
    end if

	' Deinit the stream
	err_ = beDeinitStream(state->hbeStream, state->pMP3Buffer, @dwWrite)

	' Check result
	if(err_ <> BE_ERR_SUCCESSFUL) then
		beCloseStream(state->hbeStream)

		return FMOD_ERR_PLUGIN
	end if

	' Are there any bytes returned from the DeInit call?
	' If so, write them to disk
	if( dwWrite) then
		if( fwrite( state->pMP3Buffer, 1, dwWrite, state->mFP ) <> dwWrite ) then
			return FMOD_ERR_FILE_BAD
		end if
	end if

	' close the MP3 Stream
	beCloseStream( state->hbeStream )

    if (state->pWAVBuffer) then
        deallocate(state->pWAVBuffer)
        state->pWAVBuffer = 0
    end if

    if (state->pMP3Buffer) then
        deallocate(state->pMP3Buffer)
        state->pMP3Buffer = 0
    end if

    if (state->mFP) then
        fclose(state->mFP)
        state->mFP = 0
    end if

    if (state) then
        deallocate(state)
        output_->plugindata = 0
    end if

    function = FMOD_OK
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_UpdateCallback(byval output_ as FMOD_OUTPUT_STATE ptr) as FMOD_RESULT
    dim as FMOD_RESULT result = any
    dim as outputmp3_state ptr state = output_->plugindata
	dim as DWORD dwRead=0
	dim as DWORD dwWrite=0
	dim as DWORD dwDone=0
	dim as DWORD dwFileSize=0   
    dim as BE_ERR err_ = any

    /'
        Update the mixer to the interleaved buffer.
    '/
    result = output_->readfrommixer(output_, state->pWAVBuffer, state->dwSamples / sizeof(SHORT))
    if (result <> FMOD_OK) then
        return FMOD_OK
    end if
          
    dwRead = state->dwSamples * sizeof(SHORT)

	scope
		' Encode samples
		err_ = beEncodeChunk(state->hbeStream, dwRead / sizeof(SHORT), state->pWAVBuffer, state->pMP3Buffer, @dwWrite)

		' Check result
		if(err_ <> BE_ERR_SUCCESSFUL) then
			beCloseStream(state->hbeStream)
			return FMOD_ERR_PLUGIN
		end if
		
		' write dwWrite bytes that are returned in tehe pMP3Buffer to disk
		if (fwrite(state->pMP3Buffer, 1, dwWrite, state->mFP) <> dwWrite) then
			return FMOD_ERR_FILE_BAD
		end if

		dwDone += dwRead*sizeof(SHORT)
	end scope
   
    function = FMOD_OK
end function


/'
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
'/
function OutputMP3_GetHandleCallback(byval output_ as FMOD_OUTPUT_STATE ptr, byval handle as any ptr ptr) as FMOD_RESULT
    dim as outputmp3_state ptr state = output_->plugindata

    *handle = state->mFP

    function = FMOD_OK
end function


