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

 Sound played here decoded in realtime by the user with a 'decoder sound'    
 The mp3 is created as a stream opened with FMOD_OPENONLY. This is the       
 'decoder sound'.  The playback sound is a 16bit PCM FMOD_OPENUSER created   
 sound with a pcm read callback.  When the callback happens, we call readData
 on the decoder sound and use the pcmreadcallback data pointer as the parameter.
==============================================================================================='/
#include "windows.bi"
#include "crt.bi"

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

sub ERRCHECK(byval result as FMOD_RESULT)
    if (result <> FMOD_OK) then
        printf(!"FMOD error! (%d) %s\n", result, FMOD_ErrorString(result))
        end -1
end if
end sub

dim shared as FMOD_SOUND       ptr decodesound = 0
dim shared as CRITICAL_SECTION  decodecrit = any

function pcmreadcallback(byval sound as FMOD_SOUND ptr, byval data_ as any ptr, byval datalen as uinteger) as FMOD_RESULT
    dim as FMOD_RESULT result = any
    dim as uinteger lread = any

    EnterCriticalSection(@decodecrit)
    
    if (decodesound = 0) then
        return FMOD_ERR_FILE_EOF
    end if

    result = FMOD_Sound_ReadData(decodesound, data_, datalen, @lread)

    if (result = FMOD_ERR_FILE_EOF) then   /' Handle looping. '/
        FMOD_Sound_SeekData(decodesound, 0)
        
        datalen -= lread

        result = FMOD_Sound_ReadData(decodesound, data_ + lread, datalen, @lread)
    end if

    LeaveCriticalSection(@decodecrit)

    return FMOD_OK
end function


function pcmsetposcallback(byval sound as FMOD_SOUND ptr, byval subsound as integer, byval position as uinteger, byval postype as FMOD_TIMEUNIT) as FMOD_RESULT
    /'
        The timeunit will usually be FMOD_TIMEUNIT_PCM, as FMOD will convert the user's timeunit to pcm for the codec.
        If it is not, do the necessary conversion of whatever is coming in, to PCM samples for Sound_seekData.
    '/
    
    FMOD_Sound_SeekData(decodesound, position)

    return FMOD_OK
end function

' program start
    dim as FMOD_SYSTEM             ptr fsystem = any
    dim as FMOD_SOUND              ptr sound = any
    dim as FMOD_CHANNEL            ptr channel = 0
    dim as FMOD_RESULT             result = any
    dim as string                     key
    dim as FMOD_CREATESOUNDEXINFO  createsoundexinfo
    dim as uinteger            version = any, decodesound_lengthbytes = 0
    dim as integer                     decodesound_channels = any
    dim as float                   decodesound_rate = any

    /'
        Create a System object and initialize.
    '/
    result = FMOD_System_Create(@fsystem)
    ERRCHECK(result)

    result = FMOD_System_GetVersion(fsystem, @version)
    ERRCHECK(result)

    if (version < FMOD_VERSION) then
        printf(!"Error!  You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION)
        end 0
    end if

    result = FMOD_System_Init(fsystem, 32, FMOD_INIT_NORMAL, 0)
    ERRCHECK(result)

    InitializeCriticalSection(@decodecrit)

    /'
        First create the 'decoder sound'.  Note it is a stream that does not initially read any data, because FMOD_OPENONLY has been specified.
        We could use createSound instead of createStream but that would allocate memory for the whole sound which is a waste.
    '/
    result = FMOD_System_CreateStream(fsystem, "media/wave.mp3", FMOD_OPENONLY or FMOD_LOOP_NORMAL or FMOD_LOWMEM or FMOD_CREATESTREAM, 0, @decodesound) 
    ERRCHECK(result)

    result = FMOD_Sound_GetLength(decodesound, @decodesound_lengthbytes, FMOD_TIMEUNIT_PCMBYTES)
    ERRCHECK(result)

    result = FMOD_Sound_GetFormat(decodesound, 0, 0, @decodesound_channels, 0)
    ERRCHECK(result)

    result = FMOD_Sound_GetDefaults(decodesound, @decodesound_rate, 0, 0, 0)
    ERRCHECK(result)
        
    /'
        Now create a user created PCM stream that we will feed data into, and play.
    '/
    createsoundexinfo.cbsize            = sizeof(FMOD_CREATESOUNDEXINFO)              /' required. '/
    createsoundexinfo.decodebuffersize  = 44100                                       /' Chunk size of stream update in samples.  This will be the amount of data passed to the user callback. '/
    createsoundexinfo.numchannels       = decodesound_channels                        /' Number of channels in the sound. '/
    createsoundexinfo.length            = decodesound_lengthbytes                     /' Length of PCM data in bytes of whole song.  -1 = infinite. '/
    createsoundexinfo.defaultfrequency  = decodesound_rate                       /' Default playback rate of sound. '/
    createsoundexinfo.format            = FMOD_SOUND_FORMAT_PCM16                     /' Data format of sound. '/
    createsoundexinfo.pcmreadcallback   = @pcmreadcallback                             /' User callback for reading. '/
    createsoundexinfo.pcmsetposcallback = @pcmsetposcallback                           /' User callback for seeking. '/

    result = FMOD_System_CreateStream(fsystem, 0, FMOD_2D or FMOD_OPENUSER or FMOD_LOOP_NORMAL, @createsoundexinfo, @sound)
    ERRCHECK(result)

    printf(!"============================================================================\n")
    printf(!"Manual Decode example.  Copyright (c) Firelight Technologies 2004-2011.\n")
    printf(!"============================================================================\n")
    printf(!"Sound played here decoded in realtime by the user with a 'decoder sound'    \n")
    printf(!"The mp3 is created as a stream opened with FMOD_OPENONLY. This is the       \n")
    printf(!"'decoder sound'.  The playback sound is a 16bit PCM FMOD_OPENUSER created   \n")
    printf(!"sound with a pcm read callback.  When the callback happens, we call readData\n")
    printf(!"on the decoder sound and use the pcmreadcallback data pointer as the parameter.\n")
    printf(!"============================================================================\n")
    printf(!"\n")
    printf(!"Press space to pause, Esc to quit\n")
    printf(!"Press '<' to rewind 1 second.\n")
    printf(!"Press '>' to fast forward 1 second.\n")
    printf(!"\n")

    /'
        Play the sound.
    '/

    result = FMOD_System_PlaySound(fsystem, FMOD_CHANNEL_FREE, sound, 0, @channel)
    ERRCHECK(result)

    /'
        Main loop.
    '/
    do
key = inkey
            select case key
                case " " :
                    dim as integer paused = any
                    FMOD_Channel_GetPaused(channel, @paused)
paused xor=1
                    FMOD_Channel_SetPaused(channel, paused)
                case "<" :
                    dim as uinteger position = any

                    FMOD_Channel_GetPosition(channel, @position, FMOD_TIMEUNIT_MS)
                    if (position >= 1000) then
                        position -= 1000
                    end if
                    FMOD_Channel_SetPosition(channel, position, FMOD_TIMEUNIT_MS)
                case ">" :
                    dim as uinteger position = any

                    FMOD_Channel_GetPosition(channel, @position, FMOD_TIMEUNIT_MS)
                    position += 1000
                    FMOD_Channel_SetPosition(channel, position, FMOD_TIMEUNIT_MS)
        end select

        FMOD_System_Update(fsystem)

        if (channel) then
            dim as uinteger ms = any
            dim as uinteger lenms = any
            dim as integer          playing = any
            dim as integer          paused = any

            FMOD_Channel_IsPlaying(channel, @playing)
            if ((result <> FMOD_OK) and (result <> FMOD_ERR_INVALID_HANDLE) and (result <> FMOD_ERR_CHANNEL_STOLEN)) then
                ERRCHECK(result)
            end if

            result = FMOD_Channel_GetPaused(channel, @paused)
            if ((result <> FMOD_OK) and (result <> FMOD_ERR_INVALID_HANDLE) and (result <> FMOD_ERR_CHANNEL_STOLEN)) then
                ERRCHECK(result)
            end if

            result = FMOD_Channel_GetPosition(channel, @ms, FMOD_TIMEUNIT_MS)
            if ((result <> FMOD_OK) and (result <> FMOD_ERR_INVALID_HANDLE) and (result <> FMOD_ERR_CHANNEL_STOLEN)) then
                ERRCHECK(result)
            end if

            result = FMOD_Sound_GetLength(sound, @lenms, FMOD_TIMEUNIT_MS)
            if ((result <> FMOD_OK) and (result <> FMOD_ERR_INVALID_HANDLE) and (result <> FMOD_ERR_CHANNEL_STOLEN)) then
                ERRCHECK(result)
            end if

            printf(!"Time %02d:%02d:%02d/%02d:%02d:%02d : %s\r", _
ms \ 1000 \ 60, ms \ 1000 mod 60, ms \ 10 mod 100, lenms \ 1000 \ 60, lenms \ 1000 mod 60, lenms \ 10 mod 100, _
iif(paused,@"Paused",iif(playing,@"Playing",@"Stopped")))
        end if

        Sleep(20)

    loop while (key <> chr(27))

    printf(!"\n")

    EnterCriticalSection(@decodecrit)
    scope
        /'
            Remove the sound - wait! it might be still in use!
            Instead of releasing the decode sound first we could release it last, but this protection is here to make the issue obvious.
        '/
        result = FMOD_Sound_Release(decodesound)
        ERRCHECK(result)
        decodesound = 0    /' This will make the read callback fail from now on. '/
    end scope
    LeaveCriticalSection(@decodecrit)

    /'
        Shut down
    '/
    result = FMOD_Sound_Release(sound)
    ERRCHECK(result)
    result = FMOD_System_Close(fsystem)
    ERRCHECK(result)
    result = FMOD_System_Release(fsystem)
    ERRCHECK(result)

    DeleteCriticalSection(@decodecrit)
